Skip to main content

webrender/
render_task_graph.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5//! This module contains the render task graph.
6//!
7//! Code associated with creating specific render tasks is in the render_task
8//! module.
9
10use api::units::*;
11use api::ImageFormat;
12use crate::gpu_types::ImageSource;
13use crate::internal_types::{TextureSource, CacheTextureId, FastHashMap, FastHashSet, FrameId};
14use crate::internal_types::size_of_frame_vec;
15use crate::render_task::{StaticRenderTaskSurface, RenderTaskLocation, RenderTask, SubTask};
16use crate::render_target::RenderTargetKind;
17use crate::render_task::{RenderTaskData, RenderTaskKind};
18use crate::renderer::GpuBufferAddress;
19use crate::renderer::GpuBufferBuilder;
20use crate::resource_cache::ResourceCache;
21use crate::texture_pack::GuillotineAllocator;
22use crate::prim_store::DeferredResolve;
23use crate::image_source::{resolve_image, resolve_cached_render_task};
24use smallvec::SmallVec;
25use topological_sort::TopologicalSort;
26
27use crate::render_target::{RenderTargetList, PictureCacheTarget, RenderTarget};
28use crate::util::{Allocation, VecHelper};
29use std::{usize, f32};
30
31use crate::internal_types::{FrameVec, FrameMemory};
32
33#[cfg(test)]
34use crate::frame_allocator::FrameAllocator;
35
36/// If we ever need a larger texture than the ideal, we better round it up to a
37/// reasonable number in order to have a bit of leeway in case the size of this
38/// this target is changing each frame.
39const TEXTURE_DIMENSION_MASK: i32 = 0xFF;
40
41/// Allows initializing a render task directly into the render task buffer.
42///
43/// See utils::VecHelpers. RenderTask is fairly large so avoiding the move when
44/// pushing into the vector can save a lot of expensive memcpys on pages with many
45/// render tasks.
46pub struct RenderTaskAllocation<'a> {
47    pub alloc: Allocation<'a, RenderTask>,
48}
49
50impl<'l> RenderTaskAllocation<'l> {
51    #[inline(always)]
52    pub fn init(self, value: RenderTask) -> RenderTaskId {
53        RenderTaskId::from_index(self.alloc.init(value))
54    }
55}
56
57#[derive(Copy, Clone, PartialEq, Eq, Hash)]
58#[derive(MallocSizeOf)]
59#[cfg_attr(feature = "capture", derive(Serialize))]
60#[cfg_attr(feature = "replay", derive(Deserialize))]
61pub struct RenderTaskId {
62    pub index: u32,
63    pub sub_rect_index: u16,
64}
65
66impl RenderTaskId {
67    pub const INVALID: RenderTaskId = RenderTaskId {
68        index: u32::MAX,
69        sub_rect_index: u16::MAX,
70    };
71
72    #[inline]
73    fn from_index(index: usize) -> Self {
74        RenderTaskId { index: index as u32, sub_rect_index: u16::MAX }
75    }
76
77    #[inline]
78    pub fn index(&self) -> usize {
79        self.index as usize
80    }
81
82    pub fn has_sub_rect(&self) -> bool {
83        self.sub_rect_index != u16::MAX
84    }
85}
86
87impl std::fmt::Debug for RenderTaskId {
88    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
89        if *self == Self::INVALID {
90            write!(f, "<invalid>")
91        } else {
92            write!(f, "#{}", self.index)
93        }
94    }
95}
96
97#[cfg_attr(feature = "capture", derive(Serialize))]
98#[cfg_attr(feature = "replay", derive(Deserialize))]
99#[derive(Clone)]
100pub struct SubTaskRange(std::ops::Range<u32>);
101
102impl SubTaskRange {
103    pub fn empty() -> Self { SubTaskRange(0..0) }
104    pub fn is_empty(&self) -> bool { self.0.is_empty() }
105}
106
107impl std::fmt::Debug for SubTaskRange {
108    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
109        if self.is_empty() {
110            write!(f, "<empty>")
111        } else {
112            self.0.fmt(f)
113        }
114    }
115}
116
117#[derive(Copy, Clone, PartialEq, Eq, Hash)]
118#[derive(MallocSizeOf)]
119#[cfg_attr(feature = "capture", derive(Serialize))]
120#[cfg_attr(feature = "replay", derive(Deserialize))]
121pub struct SubTaskId(u32);
122
123impl std::fmt::Debug for SubTaskId {
124    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
125        write!(f, "#{}", self.0)
126    }
127}
128
129impl Iterator for SubTaskRange {
130    type Item = SubTaskId;
131    fn next(&mut self) -> Option<Self::Item> {
132        Some(SubTaskId(self.0.next()?))
133    }
134}
135
136#[cfg_attr(feature = "capture", derive(Serialize))]
137#[cfg_attr(feature = "replay", derive(Deserialize))]
138#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
139pub struct PassId(usize);
140
141impl PassId {
142    pub const MIN: PassId = PassId(0);
143    pub const MAX: PassId = PassId(!0 - 1);
144    pub const INVALID: PassId = PassId(!0 - 2);
145}
146
147/// An internal representation of a dynamic surface that tasks can be
148/// allocated into. Maintains some extra metadata about each surface
149/// during the graph build.
150#[cfg_attr(feature = "capture", derive(Serialize))]
151#[cfg_attr(feature = "replay", derive(Deserialize))]
152struct Surface {
153    /// Whether this is a color or alpha render target
154    kind: RenderTargetKind,
155    /// Allocator for this surface texture
156    allocator: GuillotineAllocator,
157    /// We can only allocate into this for reuse if it's a shared surface
158    is_shared: bool,
159    /// The lifetime group of this surface: only tasks with a matching
160    /// lifetime_group can share it, to avoid holding the surface longer
161    /// than necessary.
162    lifetime_group: PassId,
163    /// Reference count: number of tasks whose individual free_after will trigger
164    /// a decrement. Surface is returned to the pool when this reaches 0.
165    pending_frees: usize,
166}
167
168impl Surface {
169    /// Allocate a rect within a shared surfce. Returns None if the
170    /// format doesn't match, or allocation fails.
171    fn alloc_rect(
172        &mut self,
173        size: DeviceIntSize,
174        kind: RenderTargetKind,
175        is_shared: bool,
176        lifetime_group: PassId,
177    ) -> Option<DeviceIntPoint> {
178        if self.kind == kind && self.is_shared == is_shared && self.lifetime_group == lifetime_group {
179            self.allocator
180                .allocate(&size)
181                .map(|(_slice, origin)| origin)
182        } else {
183            None
184        }
185    }
186}
187
188/// A sub-pass can draw to either a dynamic (temporary render target) surface,
189/// or a persistent surface (texture or picture cache).
190#[cfg_attr(feature = "capture", derive(Serialize))]
191#[cfg_attr(feature = "replay", derive(Deserialize))]
192#[derive(Debug)]
193pub enum SubPassSurface {
194    /// A temporary (intermediate) surface.
195    Dynamic {
196        /// The renderer texture id
197        texture_id: CacheTextureId,
198        /// Color / alpha render target
199        target_kind: RenderTargetKind,
200        /// The rectangle occupied by tasks in this surface. Used as a clear
201        /// optimization on some GPUs.
202        used_rect: DeviceIntRect,
203    },
204    Persistent {
205        /// Reference to the texture or picture cache surface being drawn to.
206        surface: StaticRenderTaskSurface,
207    },
208}
209
210/// A subpass is a specific render target, and a list of tasks to draw to it.
211#[cfg_attr(feature = "capture", derive(Serialize))]
212#[cfg_attr(feature = "replay", derive(Deserialize))]
213pub struct SubPass {
214    /// The surface this subpass draws to
215    pub surface: SubPassSurface,
216    /// The tasks assigned to this subpass.
217    pub task_ids: FrameVec<RenderTaskId>,
218}
219
220/// A pass expresses dependencies between tasks. Each pass consists of a number
221/// of subpasses.
222#[cfg_attr(feature = "capture", derive(Serialize))]
223#[cfg_attr(feature = "replay", derive(Deserialize))]
224pub struct Pass {
225    /// The tasks assigned to this render pass
226    pub task_ids: FrameVec<RenderTaskId>,
227    /// The subpasses that make up this dependency pass
228    pub sub_passes: FrameVec<SubPass>,
229    /// A list of intermediate surfaces that can be invalidated after
230    /// this pass completes.
231    pub textures_to_invalidate: FrameVec<CacheTextureId>,
232}
233
234#[cfg_attr(feature = "capture", derive(Serialize))]
235#[cfg_attr(feature = "replay", derive(Deserialize))]
236pub struct TaskSubRect {
237    pub sub_rect: DeviceIntRect,
238    source_task: RenderTaskId,
239    uv_address: GpuBufferAddress,
240}
241
242/// The RenderTaskGraph is the immutable representation of the render task graph. It is
243/// built by the RenderTaskGraphBuilder, and is constructed once per frame.
244#[cfg_attr(feature = "capture", derive(Serialize))]
245#[cfg_attr(feature = "replay", derive(Deserialize))]
246pub struct RenderTaskGraph {
247    /// List of tasks added to the graph
248    pub tasks: FrameVec<RenderTask>,
249    /// List of sub-tasks, executing after the regular tasks for a given
250    /// pass.
251    pub sub_tasks: FrameVec<SubTask>,
252
253    pub sub_rects: FrameVec<TaskSubRect>,
254
255    /// The passes that were created, based on dependencies between tasks
256    pub passes: FrameVec<Pass>,
257
258    /// Current frame id, used for debug validation
259    frame_id: FrameId,
260
261    /// GPU specific data for each task that is made available to shaders
262    pub task_data: FrameVec<RenderTaskData>,
263
264    /// Total number of intermediate surfaces that will be drawn to, used for test validation.
265    #[cfg(test)]
266    surface_count: usize,
267
268    /// Total number of real allocated textures that will be drawn to, used for test validation.
269    #[cfg(test)]
270    unique_surfaces: FastHashSet<CacheTextureId>,
271}
272
273/// The persistent interface that is used during frame building to construct the
274/// frame graph.
275pub struct RenderTaskGraphBuilder {
276    /// List of tasks added to the builder
277    tasks: Vec<RenderTask>,
278    /// List of sub-tasks added to the builder
279    sub_tasks: Vec<SubTask>,
280    /// render task ids can optionally refer to sub-rects of a render task
281    /// by indexing into this vector.
282    sub_rects: Vec<TaskSubRect>,
283
284    /// List of task roots
285    roots: FastHashSet<RenderTaskId>,
286
287    /// Current frame id, used for debug validation
288    frame_id: FrameId,
289
290    /// A list of texture surfaces that can be freed at the end of a pass. Retained
291    /// here to reduce heap allocations.
292    textures_to_free: FastHashSet<CacheTextureId>,
293
294    /// Set of task ids already processed for freeing in the current pass,
295    /// used to avoid double-counting when multiple parents reference
296    /// the same child task.
297    freed_tasks: FastHashSet<RenderTaskId>,
298
299    // Keep a map of `texture_id` to metadata about surfaces that are currently
300    // borrowed from the render target pool.
301    active_surfaces: FastHashMap<CacheTextureId, Surface>,
302}
303
304impl RenderTaskGraphBuilder {
305    /// Construct a new graph builder. Typically constructed once and maintained
306    /// over many frames, to avoid extra heap allocations where possible.
307    pub fn new() -> Self {
308        RenderTaskGraphBuilder {
309            tasks: Vec::new(),
310            sub_tasks: Vec::new(),
311            sub_rects: Vec::new(),
312            roots: FastHashSet::default(),
313            frame_id: FrameId::INVALID,
314            textures_to_free: FastHashSet::default(),
315            freed_tasks: FastHashSet::default(),
316            active_surfaces: FastHashMap::default(),
317        }
318    }
319
320    pub fn frame_id(&self) -> FrameId {
321        self.frame_id
322    }
323
324    /// Begin a new frame
325    pub fn begin_frame(&mut self, frame_id: FrameId) {
326        self.frame_id = frame_id;
327        self.roots.clear();
328    }
329
330    /// Get immutable access to a task
331    // TODO(gw): There's only a couple of places that existing code needs to access
332    //           a task during the building step. Perhaps we can remove this?
333    pub fn get_task(
334        &self,
335        task_id: RenderTaskId,
336    ) -> &RenderTask {
337        &self.tasks[task_id.index as usize]
338    }
339
340    /// Get mutable access to a task
341    // TODO(gw): There's only a couple of places that existing code needs to access
342    //           a task during the building step. Perhaps we can remove this?
343    pub fn get_task_mut(
344        &mut self,
345        task_id: RenderTaskId,
346    ) -> &mut RenderTask {
347        &mut self.tasks[task_id.index as usize]
348    }
349
350    /// Add a new task to the graph.
351    pub fn add(&mut self) -> RenderTaskAllocation {
352        // Assume every task is a root to start with
353        self.roots.insert(
354            RenderTaskId::from_index(self.tasks.len()),
355        );
356
357        RenderTaskAllocation {
358            alloc: self.tasks.alloc(),
359        }
360    }
361
362    pub fn begin_sub_tasks(&self) -> SubTaskRange {
363        let first = self.sub_tasks.len() as u32;
364        SubTaskRange(first..first)
365    }
366
367    pub fn push_sub_task(&mut self, in_range: &mut SubTaskRange, task: SubTask) {
368        // Check that we are pushing sub-tasks as contiguous sequences.
369        assert_eq!(in_range.0.end as usize, self.sub_tasks.len());
370        in_range.0.end += 1;
371
372        self.sub_tasks.push(task);
373    }
374
375    /// Express a dependency, such that `task_id` depends on `input` as a texture source.
376    pub fn add_dependency(
377        &mut self,
378        task_id: RenderTaskId,
379        input: RenderTaskId,
380    ) {
381        self.tasks[task_id.index as usize].children.push(input);
382
383        // Once a task is an input, it's no longer a root
384        self.roots.remove(&input);
385    }
386
387    pub fn add_sub_rect(&mut self, source_task: RenderTaskId, sub_rect: &DeviceIntRect) -> RenderTaskId {
388        assert!(self.sub_rects.len() < u16::MAX as usize);
389        if source_task == RenderTaskId::INVALID {
390            return RenderTaskId::INVALID;
391        }
392
393        let sub_rect_index = self.sub_rects.len() as u16;
394        self.sub_rects.push(TaskSubRect {
395            source_task,
396            sub_rect: *sub_rect,
397            uv_address: GpuBufferAddress::INVALID,
398        });
399
400        RenderTaskId {
401            index: source_task.index,
402            sub_rect_index,
403        }
404    }
405
406    /// End the graph building phase and produce the immutable task graph for this frame
407    pub fn end_frame(
408        &mut self,
409        resource_cache: &mut ResourceCache,
410        gpu_buffers: &mut GpuBufferBuilder,
411        deferred_resolves: &mut FrameVec<DeferredResolve>,
412        max_shared_surface_size: i32,
413        memory: &FrameMemory,
414    ) -> RenderTaskGraph {
415        // Copy the render tasks over to the immutable graph output
416        let task_count = self.tasks.len();
417
418        // Copy from the frame_builder's task vector to the frame's instead of stealing it
419        // because they use different memory allocators. TODO: The builder should use the
420        // frame allocator, however since the builder lives longer than the frame, it's a
421        // bit more risky to do so.
422        let mut tasks = memory.new_vec_with_capacity(task_count);
423        for task in self.tasks.drain(..) {
424            tasks.push(task)
425        }
426
427        let mut sub_tasks = memory.new_vec_with_capacity(self.sub_tasks.len());
428        for task in self.sub_tasks.drain(..) {
429            sub_tasks.push(task)
430        }
431
432        let mut graph = RenderTaskGraph {
433            tasks,
434            sub_tasks,
435            sub_rects: memory.new_vec(),
436            passes: memory.new_vec(),
437            task_data: memory.new_vec_with_capacity(task_count),
438            frame_id: self.frame_id,
439            #[cfg(test)]
440            surface_count: 0,
441            #[cfg(test)]
442            unique_surfaces: FastHashSet::default(),
443        };
444
445        // First, use a topological sort of the dependency graph to split the task set in to
446        // a list of passes. This is necessary because when we have a complex graph (e.g. due
447        // to a large number of sibling backdrop-filter primitives) traversing it via a simple
448        // recursion can be too slow. The second pass determines when the last time a render task
449        // is used as an input, and assigns what pass the surface backing that render task can
450        // be freed (the surface is then returned to the render target pool and may be aliased
451        // or reused during subsequent passes).
452
453        let mut pass_count = 0;
454        let mut passes = memory.new_vec();
455        let mut task_sorter = TopologicalSort::<RenderTaskId>::new();
456
457        // Iterate the task list, and add all the dependencies to the topo sort
458        for (parent_id, task) in graph.tasks.iter().enumerate() {
459            let parent_id = RenderTaskId::from_index(parent_id);
460
461            for child_id in &task.children {
462                task_sorter.add_dependency(
463                    parent_id,
464                    *child_id,
465                );
466            }
467        }
468
469        // Pop the sorted passes off the topological sort
470        loop {
471            // Get the next set of tasks that can be drawn
472            let tasks = task_sorter.pop_all();
473
474            // If there are no tasks left, we're done
475            if tasks.is_empty() {
476                // If the task sorter itself isn't empty but we couldn't pop off any
477                // tasks, that implies a circular dependency in the task graph
478                assert!(task_sorter.is_empty());
479                break;
480            } else {
481                // Assign the `render_on` field to the task
482                for task_id in &tasks {
483                    graph.tasks[task_id.index as usize].render_on = PassId(pass_count);
484                }
485
486                // Store the task list for this pass, used later for `assign_free_pass`.
487                passes.push(tasks);
488                pass_count += 1;
489            }
490        }
491
492        // Always create at least one pass for root tasks
493        pass_count = pass_count.max(1);
494
495        // Determine which pass each task can be freed on, which depends on which is
496        // the last task that has this as an input. This must be done in top-down
497        // pass order to ensure that RenderTaskLocation::Existing references are
498        // visited in the correct order
499        for pass in passes {
500            for task_id in pass {
501                assign_free_pass(
502                    task_id,
503                    &mut graph,
504                );
505            }
506        }
507
508        // Construct passes array for tasks to be assigned to below
509        for _ in 0 .. pass_count {
510            graph.passes.push(Pass {
511                task_ids: memory.new_vec(),
512                sub_passes: memory.new_vec(),
513                textures_to_invalidate: memory.new_vec(),
514            });
515        }
516
517        // Assign tasks to each pass based on their `render_on` attribute
518        for (index, task) in graph.tasks.iter().enumerate() {
519            if task.kind.is_a_rendering_operation() {
520                let id = RenderTaskId::from_index(index);
521                graph.passes[task.render_on.0].task_ids.push(id);
522            }
523        }
524
525        // At this point, tasks are assigned to each dependency pass. Now we
526        // can go through each pass and create sub-passes, assigning each task
527        // to a target and destination rect.
528        assert!(self.active_surfaces.is_empty());
529
530        for (pass_id, pass) in graph.passes.iter_mut().enumerate().rev() {
531            assert!(self.textures_to_free.is_empty());
532
533            // Phase 1: Allocate all tasks in this pass to surfaces, incrementing
534            // pending_frees on each surface for every task (including Existing).
535            for task_id in &pass.task_ids {
536
537                let task_location = graph.tasks[task_id.index as usize].location.clone();
538
539                match task_location {
540                    RenderTaskLocation::Unallocated { size } => {
541                        let task = &mut graph.tasks[task_id.index as usize];
542
543                        let mut location = None;
544                        let kind = task.kind.target_kind();
545
546                        let can_use_shared_surface =
547                            task.kind.can_use_shared_surface();
548
549                        if can_use_shared_surface {
550                            // If we can use a shared surface, step through the existing shared
551                            // surfaces for this subpass, and see if we can allocate the task
552                            // to one of these targets.
553                            for sub_pass in &mut pass.sub_passes {
554                                if let SubPassSurface::Dynamic { texture_id, ref mut used_rect, .. } = sub_pass.surface {
555                                    let surface = self.active_surfaces.get_mut(&texture_id).unwrap();
556                                    if let Some(p) = surface.alloc_rect(size, kind, true, task.free_after) {
557                                        surface.pending_frees += 1;
558                                        location = Some((texture_id, p));
559                                        *used_rect = used_rect.union(&DeviceIntRect::from_origin_and_size(p, size));
560                                        sub_pass.task_ids.push(*task_id);
561                                        break;
562                                    }
563                                }
564                            }
565                        }
566
567                        if location.is_none() {
568                            // If it wasn't possible to allocate the task to a shared surface, get a new
569                            // render target from the resource cache pool/
570
571                            // If this is a really large task, don't bother allocating it as a potential
572                            // shared surface for other tasks.
573
574                            let can_use_shared_surface = can_use_shared_surface &&
575                                size.width <= max_shared_surface_size &&
576                                size.height <= max_shared_surface_size;
577
578                            let surface_size = if can_use_shared_surface {
579                                DeviceIntSize::new(
580                                    max_shared_surface_size,
581                                    max_shared_surface_size,
582                                )
583                            } else {
584                                // Round up size here to avoid constant re-allocs during resizing
585                                DeviceIntSize::new(
586                                    (size.width + TEXTURE_DIMENSION_MASK) & !TEXTURE_DIMENSION_MASK,
587                                    (size.height + TEXTURE_DIMENSION_MASK) & !TEXTURE_DIMENSION_MASK,
588                                )
589                            };
590
591                            if surface_size.is_empty() {
592                                // We would panic in the guillotine allocator. Instead, panic here
593                                // with some context.
594                                let task_name = graph.tasks[task_id.index as usize].kind.as_str();
595                                panic!("{} render task has invalid size {:?}", task_name, surface_size);
596                            }
597
598                            let format = match kind {
599                                RenderTargetKind::Color => ImageFormat::RGBA8,
600                                RenderTargetKind::Alpha => ImageFormat::R8,
601                            };
602
603                            // Get render target of appropriate size and format from resource cache
604                            let texture_id = resource_cache.get_or_create_render_target_from_pool(
605                                surface_size,
606                                format,
607                            );
608
609                            // Allocate metadata we need about this surface while it's active
610                            let mut surface = Surface {
611                                kind,
612                                allocator: GuillotineAllocator::new(Some(surface_size)),
613                                is_shared: can_use_shared_surface,
614                                lifetime_group: task.free_after,
615                                pending_frees: 1,
616                            };
617
618                            // Allocation of the task must fit in this new surface!
619                            let p = surface.alloc_rect(
620                                size,
621                                kind,
622                                can_use_shared_surface,
623                                task.free_after,
624                            ).expect("bug: alloc must succeed!");
625
626                            location = Some((texture_id, p));
627
628                            // Store the metadata about this newly active surface. We should never
629                            // get a target surface with the same texture_id as a currently active surface.
630                            let _prev_surface = self.active_surfaces.insert(texture_id, surface);
631                            assert!(_prev_surface.is_none());
632
633                            // Store some information about surface allocations if in test mode
634                            #[cfg(test)]
635                            {
636                                graph.surface_count += 1;
637                                graph.unique_surfaces.insert(texture_id);
638                            }
639
640                            let mut task_ids = memory.new_vec();
641                            task_ids.push(*task_id);
642
643                            // Add the target as a new subpass for this render pass.
644                            pass.sub_passes.push(SubPass {
645                                surface: SubPassSurface::Dynamic {
646                                    texture_id,
647                                    target_kind: kind,
648                                    used_rect: DeviceIntRect::from_origin_and_size(p, size),
649                                },
650                                task_ids,
651                            });
652                        }
653
654                        // By now, we must have allocated a surface and rect for this task, so assign it!
655                        assert!(location.is_some());
656                        task.location = RenderTaskLocation::Dynamic {
657                            texture_id: location.unwrap().0,
658                            rect: DeviceIntRect::from_origin_and_size(location.unwrap().1, size),
659                        };
660                    }
661                    RenderTaskLocation::Existing { parent_task_id, size: existing_size, .. } => {
662                        let parent_task_location = graph.tasks[parent_task_id.index as usize].location.clone();
663
664                        match parent_task_location {
665                            RenderTaskLocation::Unallocated { .. } |
666                            RenderTaskLocation::CacheRequest { .. } |
667                            RenderTaskLocation::Existing { .. } => {
668                                panic!("bug: reference to existing task must be allocated by now");
669                            }
670                            RenderTaskLocation::Dynamic { texture_id, rect, .. } => {
671                                assert_eq!(existing_size, rect.size());
672
673                                let surface = self.active_surfaces.get_mut(&texture_id).unwrap();
674                                surface.pending_frees += 1;
675
676                                let kind = graph.tasks[parent_task_id.index as usize].kind.target_kind();
677                                let mut task_ids = memory.new_vec();
678                                task_ids.push(*task_id);
679                                // A sub-pass is always created in this case, as existing tasks by definition can't be shared.
680                                pass.sub_passes.push(SubPass {
681                                    surface: SubPassSurface::Dynamic {
682                                        texture_id,
683                                        target_kind: kind,
684                                        used_rect: rect,        // clear will be skipped due to no-op check anyway
685                                    },
686                                    task_ids,
687                                });
688
689                                let task = &mut graph.tasks[task_id.index as usize];
690                                task.location = parent_task_location;
691                            }
692                            RenderTaskLocation::Static { .. } => {
693                                unreachable!("bug: not possible since we don't dup static locations");
694                            }
695                        }
696                    }
697                    RenderTaskLocation::Static { ref surface, .. } => {
698                        // No need to allocate for this surface, since it's a persistent
699                        // target. Instead, just create a new sub-pass for it.
700                        let mut task_ids = memory.new_vec();
701                        task_ids.push(*task_id);
702                        pass.sub_passes.push(SubPass {
703                            surface: SubPassSurface::Persistent {
704                                surface: surface.clone(),
705                            },
706                            task_ids,
707                        });
708                    }
709                    RenderTaskLocation::CacheRequest { .. } => {
710                        // No need to allocate nor to create a sub-path for read-only locations.
711                    }
712                    RenderTaskLocation::Dynamic { .. } => {
713                        // Dynamic tasks shouldn't be allocated by this point
714                        panic!("bug: encountered an already allocated task");
715                    }
716                }
717            }
718
719            // Phase 2: For each child task whose free_after matches this pass,
720            // decrement the pending_frees on its surface. When pending_frees
721            // reaches 0, the surface can be returned to the pool.
722            assert!(self.freed_tasks.is_empty());
723            for task_id in &pass.task_ids {
724                let task = &graph.tasks[task_id.index as usize];
725                for child_id in &task.children {
726                    let child_task = &graph.tasks[child_id.index as usize];
727                    match child_task.location {
728                        RenderTaskLocation::Unallocated { .. } |
729                        RenderTaskLocation::Existing { .. } => panic!("bug: must be allocated"),
730                        RenderTaskLocation::Dynamic { texture_id, .. } => {
731                            if child_task.free_after == PassId(pass_id) &&
732                               self.freed_tasks.insert(*child_id)
733                            {
734                                let surface = self.active_surfaces.get_mut(&texture_id).unwrap();
735                                surface.pending_frees -= 1;
736                                if surface.pending_frees == 0 {
737                                    self.textures_to_free.insert(texture_id);
738                                }
739                            }
740                        }
741                        RenderTaskLocation::Static { .. } => {}
742                        RenderTaskLocation::CacheRequest { .. } => {}
743                    }
744                }
745            }
746            self.freed_tasks.clear();
747
748            // Return no longer used textures to the pool, so that they can be reused / aliased
749            // by later passes.
750            for texture_id in self.textures_to_free.drain() {
751                resource_cache.return_render_target_to_pool(texture_id);
752                self.active_surfaces.remove(&texture_id).unwrap();
753                pass.textures_to_invalidate.push(texture_id);
754            }
755        }
756
757        if !self.active_surfaces.is_empty() {
758            graph.print();
759            // By now, all surfaces that were borrowed from the render target pool must
760            // be returned to the resource cache, or we are leaking intermediate surfaces!
761            assert!(self.active_surfaces.is_empty());
762        }
763
764        // Each task is now allocated to a surface and target rect. Write that to the
765        // GPU blocks and task_data. After this point, the graph is returned and is
766        // considered to be immutable for the rest of the frame building process.
767
768        for task in &mut graph.tasks {
769            // Check whether the render task texture and uv rects are managed externally.
770            // This is the case for image tasks and cached tasks. In both cases it
771            // results in a finding the information in the texture cache.
772            let cache_item = if let Some(ref cache_handle) = task.cache_handle {
773                Some(resolve_cached_render_task(
774                    cache_handle,
775                    resource_cache,
776                ))
777            } else if let RenderTaskKind::Image(info) = &task.kind {
778                Some(resolve_image(
779                    info.request,
780                    resource_cache,
781                    &mut gpu_buffers.f32,
782                    deferred_resolves,
783                    info.is_composited,
784                ))
785            } else {
786                // General case (non-cached non-image tasks).
787                None
788            };
789
790            if let Some(cache_item) = &cache_item {
791                task.uv_rect_handle = gpu_buffers.f32.resolve_handle(cache_item.uv_rect_handle);
792
793                // Update the render task even if the item is invalid.
794                // We'll handle it later and it's easier to not have to
795                // deal with unexpected location variants like
796                // RenderTaskLocation::CacheRequest when we do.
797                if let RenderTaskLocation::CacheRequest { .. } = &task.location {
798                    let source = cache_item.texture_id;
799                    task.location = RenderTaskLocation::Static {
800                        surface: StaticRenderTaskSurface::ReadOnly { source },
801                        rect: cache_item.uv_rect,
802                    };
803                }
804            }
805
806            // This has to be done after we do the task location fixup above.
807            let target_rect = task.get_target_rect();
808
809            // If the uv rect is not managed externally, generate it now.
810            if cache_item.is_none() {
811                let image_source = ImageSource {
812                    p0: target_rect.min.to_f32(),
813                    p1: target_rect.max.to_f32(),
814                    user_data: [0.0; 4],
815                    uv_rect_kind: task.uv_rect_kind,
816                };
817
818                let uv_rect_handle = image_source.write_gpu_blocks(&mut gpu_buffers.f32);
819                task.uv_rect_handle = gpu_buffers.f32.resolve_handle(uv_rect_handle);
820            }
821
822            // Give the render task an opportunity to add any
823            // information to the GPU cache, if appropriate.
824            task.kind.write_gpu_blocks(gpu_buffers);
825
826            graph.task_data.push(
827                task.kind.write_task_data(target_rect)
828            );
829        }
830
831        graph.sub_rects.reserve(self.sub_rects.len());
832        for item in &self.sub_rects {
833            let task = &graph.tasks[item.source_task.index()];
834            let task_rect = task.get_target_rect();
835            let rect = item.sub_rect
836                .translate(task_rect.min.to_vector())
837                .intersection_unchecked(&task_rect);
838
839            let image_source = ImageSource {
840                p0: rect.min.to_f32(),
841                p1: rect.max.to_f32(),
842                user_data: [0.0; 4],
843                uv_rect_kind: task.uv_rect_kind,
844            };
845
846            let uv_rect_handle = image_source.write_gpu_blocks(&mut gpu_buffers.f32);
847
848            graph.sub_rects.push(TaskSubRect {
849                source_task: item.source_task,
850                sub_rect: item.sub_rect,
851                uv_address: gpu_buffers.f32.resolve_handle(uv_rect_handle),
852            });
853        }
854
855        graph
856    }
857}
858
859impl RenderTaskGraph {
860    /// Print the render task graph to console
861    #[allow(dead_code)]
862    pub fn print(
863        &self,
864    ) {
865        print!("-- RenderTaskGraph --\n");
866
867        for (i, task) in self.tasks.iter().enumerate() {
868            print!("Task {} [{}]: render_on={} free_after={} children={:?} target_size={:?}\n",
869                i,
870                task.kind.as_str(),
871                task.render_on.0,
872                task.free_after.0,
873                task.children,
874                task.get_target_size(),
875            );
876        }
877
878        for (p, pass) in self.passes.iter().enumerate() {
879            print!("Pass {}:\n", p);
880
881            for (s, sub_pass) in pass.sub_passes.iter().enumerate() {
882                print!("\tSubPass {}: {:?}\n",
883                    s,
884                    sub_pass.surface,
885                );
886
887                for task_id in &sub_pass.task_ids {
888                    print!("\t\tTask {:?}\n", task_id.index);
889                }
890            }
891        }
892    }
893
894    pub fn resolve_texture(
895        &self,
896        task_id: impl Into<Option<RenderTaskId>>,
897    ) -> Option<TextureSource> {
898        let task_id = task_id.into()?;
899        let task = &self[task_id];
900
901        match task.get_texture_source() {
902            TextureSource::Invalid => None,
903            source => Some(source),
904        }
905    }
906
907    pub fn resolve_location(
908        &self,
909        task_id: impl Into<Option<RenderTaskId>>,
910    ) -> Option<(GpuBufferAddress, TextureSource)> {
911        self.resolve_impl(task_id.into()?)
912    }
913
914    fn resolve_impl(
915        &self,
916        task_id: RenderTaskId,
917    ) -> Option<(GpuBufferAddress, TextureSource)> {
918        let task = &self[task_id];
919        let texture_source = task.get_texture_source();
920
921        if let TextureSource::Invalid = texture_source {
922            return None;
923        }
924
925        let uv_address = if task_id.has_sub_rect() {
926            self.sub_rects[task_id.sub_rect_index as usize].uv_address
927        } else {
928            task.get_texture_address()
929        };
930
931        assert!(uv_address.is_valid());
932
933        Some((uv_address, texture_source))
934    }
935
936    pub fn report_memory(&self) -> usize {
937        // We can't use wr_malloc_sizeof here because the render task
938        // graph's memory is mainly backed by frame's custom allocator.
939        // So we calulate the memory footprint manually.
940
941        let mut mem = size_of_frame_vec(&self.tasks)
942            +  size_of_frame_vec(&self.task_data)
943            +  size_of_frame_vec(&self.passes);
944
945        for pass in &self.passes {
946            mem += size_of_frame_vec(&pass.task_ids)
947                + size_of_frame_vec(&pass.sub_passes)
948                + size_of_frame_vec(&pass.textures_to_invalidate);
949            for sub_pass in &pass.sub_passes {
950                mem += size_of_frame_vec(&sub_pass.task_ids);
951            }
952        }
953
954        mem
955    }
956
957    #[cfg(test)]
958    pub fn new_for_testing() -> Self {
959        let allocator = FrameAllocator::fallback();
960        RenderTaskGraph {
961            tasks: allocator.clone().new_vec(),
962            sub_tasks: allocator.clone().new_vec(),
963            sub_rects: allocator.clone().new_vec(),
964            passes: allocator.clone().new_vec(),
965            frame_id: FrameId::INVALID,
966            task_data: allocator.clone().new_vec(),
967            surface_count: 0,
968            unique_surfaces: FastHashSet::default(),
969        }
970    }
971
972    /// Return the surface and texture counts, used for testing
973    #[cfg(test)]
974    pub fn surface_counts(&self) -> (usize, usize) {
975        (self.surface_count, self.unique_surfaces.len())
976    }
977
978    /// Return current frame id, used for validation
979    #[cfg(debug_assertions)]
980    pub fn frame_id(&self) -> FrameId {
981        self.frame_id
982    }
983}
984
985/// Batching uses index access to read information about tasks
986impl std::ops::Index<RenderTaskId> for RenderTaskGraph {
987    type Output = RenderTask;
988    fn index(&self, id: RenderTaskId) -> &RenderTask {
989        &self.tasks[id.index as usize]
990    }
991}
992
993impl std::ops::Index<SubTaskId> for RenderTaskGraph {
994    type Output = SubTask;
995    fn index(&self, id: SubTaskId) -> &SubTask {
996        &self.sub_tasks[id.0 as usize]
997    }
998}
999
1000fn assign_free_pass(
1001    id: RenderTaskId,
1002    graph: &mut RenderTaskGraph,
1003) {
1004    let task = &mut graph.tasks[id.index as usize];
1005    let render_on = task.render_on;
1006
1007    let mut child_task_ids: SmallVec<[RenderTaskId; 8]> = SmallVec::new();
1008    child_task_ids.extend_from_slice(&task.children);
1009
1010    for child_id in child_task_ids {
1011        let child_location = graph.tasks[child_id.index as usize].location.clone();
1012
1013        // Each dynamic child task can free its backing surface after the last
1014        // task that references it as an input. Using min here ensures the
1015        // safe time to free this surface in the presence of multiple paths
1016        // to this task from the root(s).
1017        match child_location {
1018            RenderTaskLocation::CacheRequest { .. } => {}
1019            RenderTaskLocation::Static { .. } => {
1020                // never get freed anyway, so can leave untouched
1021                // (could validate that they remain at PassId::MIN)
1022            }
1023            RenderTaskLocation::Dynamic { .. } => {
1024                panic!("bug: should not be allocated yet");
1025            }
1026            RenderTaskLocation::Unallocated { .. } |
1027            RenderTaskLocation::Existing { .. } => {
1028                let child_task = &mut graph.tasks[child_id.index as usize];
1029                child_task.free_after = child_task.free_after.min(render_on);
1030            }
1031        }
1032    }
1033}
1034
1035/// A render pass represents a set of rendering operations that don't depend on one
1036/// another.
1037///
1038/// A render pass can have several render targets if there wasn't enough space in one
1039/// target to do all of the rendering for that pass. See `RenderTargetList`.
1040#[cfg_attr(feature = "capture", derive(Serialize))]
1041#[cfg_attr(feature = "replay", derive(Deserialize))]
1042pub struct RenderPass {
1043    /// The subpasses that describe targets being rendered to in this pass
1044    pub alpha: RenderTargetList,
1045    pub color: RenderTargetList,
1046    pub texture_cache: FastHashMap<CacheTextureId, RenderTarget>,
1047    pub picture_cache: FrameVec<PictureCacheTarget>,
1048    pub textures_to_invalidate: FrameVec<CacheTextureId>,
1049}
1050
1051impl RenderPass {
1052    /// Creates an intermediate off-screen pass.
1053    pub fn new(src: &Pass, memory: &mut FrameMemory) -> Self {
1054        RenderPass {
1055            color: RenderTargetList::new(memory.allocator()),
1056            alpha: RenderTargetList::new(memory.allocator()),
1057            texture_cache: FastHashMap::default(),
1058            picture_cache: memory.allocator().new_vec(),
1059            textures_to_invalidate: src.textures_to_invalidate.clone(),
1060        }
1061    }
1062}
1063
1064// Dump an SVG visualization of the render graph for debugging purposes
1065#[cfg(feature = "capture")]
1066pub fn dump_render_tasks_as_svg(
1067    render_tasks: &RenderTaskGraph,
1068    output: &mut dyn std::io::Write,
1069) -> std::io::Result<()> {
1070    use svg_fmt::*;
1071
1072    let node_width = 80.0;
1073    let node_height = 30.0;
1074    let vertical_spacing = 8.0;
1075    let horizontal_spacing = 20.0;
1076    let margin = 10.0;
1077    let text_size = 10.0;
1078
1079    let mut pass_rects = Vec::new();
1080    let mut nodes = vec![None; render_tasks.tasks.len()];
1081
1082    let mut x = margin;
1083    let mut max_y: f32 = 0.0;
1084
1085    #[derive(Clone)]
1086    struct Node {
1087        rect: Rectangle,
1088        label: Text,
1089        size: Text,
1090    }
1091
1092    for pass in render_tasks.passes.iter().rev() {
1093        let mut layout = VerticalLayout::new(x, margin, node_width);
1094
1095        for task_id in &pass.task_ids {
1096            let task_index = task_id.index as usize;
1097            let task = &render_tasks.tasks[task_index];
1098
1099            let rect = layout.push_rectangle(node_height);
1100
1101            let tx = rect.x + rect.w / 2.0;
1102            let ty = rect.y + 10.0;
1103
1104            let label = text(tx, ty, format!("{}", task.kind.as_str()));
1105            let size = text(tx, ty + 12.0, format!("{:?}", task.location.size()));
1106
1107            nodes[task_index] = Some(Node { rect, label, size });
1108
1109            layout.advance(vertical_spacing);
1110        }
1111
1112        pass_rects.push(layout.total_rectangle());
1113
1114        x += node_width + horizontal_spacing;
1115        max_y = max_y.max(layout.y + margin);
1116    }
1117
1118    let mut links = Vec::new();
1119    for node_index in 0..nodes.len() {
1120        if nodes[node_index].is_none() {
1121            continue;
1122        }
1123
1124        let task = &render_tasks.tasks[node_index];
1125        for dep in &task.children {
1126            let dep_index = dep.index as usize;
1127
1128            if let (&Some(ref node), &Some(ref dep_node)) = (&nodes[node_index], &nodes[dep_index]) {
1129                links.push((
1130                    dep_node.rect.x + dep_node.rect.w,
1131                    dep_node.rect.y + dep_node.rect.h / 2.0,
1132                    node.rect.x,
1133                    node.rect.y + node.rect.h / 2.0,
1134                ));
1135            }
1136        }
1137    }
1138
1139    let svg_w = x + margin;
1140    let svg_h = max_y + margin;
1141    writeln!(output, "{}", BeginSvg { w: svg_w, h: svg_h })?;
1142
1143    // Background.
1144    writeln!(output,
1145        "    {}",
1146        rectangle(0.0, 0.0, svg_w, svg_h)
1147            .inflate(1.0, 1.0)
1148            .fill(rgb(50, 50, 50))
1149    )?;
1150
1151    // Passes.
1152    for rect in pass_rects {
1153        writeln!(output,
1154            "    {}",
1155            rect.inflate(3.0, 3.0)
1156                .border_radius(4.0)
1157                .opacity(0.4)
1158                .fill(black())
1159        )?;
1160    }
1161
1162    // Links.
1163    for (x1, y1, x2, y2) in links {
1164        dump_task_dependency_link(output, x1, y1, x2, y2);
1165    }
1166
1167    // Tasks.
1168    for node in &nodes {
1169        if let Some(node) = node {
1170            writeln!(output,
1171                "    {}",
1172                node.rect
1173                    .clone()
1174                    .fill(black())
1175                    .border_radius(3.0)
1176                    .opacity(0.5)
1177                    .offset(0.0, 2.0)
1178            )?;
1179            writeln!(output,
1180                "    {}",
1181                node.rect
1182                    .clone()
1183                    .fill(rgb(200, 200, 200))
1184                    .border_radius(3.0)
1185                    .opacity(0.8)
1186            )?;
1187
1188            writeln!(output,
1189                "    {}",
1190                node.label
1191                    .clone()
1192                    .size(text_size)
1193                    .align(Align::Center)
1194                    .color(rgb(50, 50, 50))
1195            )?;
1196            writeln!(output,
1197                "    {}",
1198                node.size
1199                    .clone()
1200                    .size(text_size * 0.7)
1201                    .align(Align::Center)
1202                    .color(rgb(50, 50, 50))
1203            )?;
1204        }
1205    }
1206
1207    writeln!(output, "{}", EndSvg)
1208}
1209
1210#[allow(dead_code)]
1211fn dump_task_dependency_link(
1212    output: &mut dyn std::io::Write,
1213    x1: f32, y1: f32,
1214    x2: f32, y2: f32,
1215) {
1216    use svg_fmt::*;
1217
1218    // If the link is a straight horizontal line and spans over multiple passes, it
1219    // is likely to go straight though unrelated nodes in a way that makes it look like
1220    // they are connected, so we bend the line upward a bit to avoid that.
1221    let simple_path = (y1 - y2).abs() > 1.0 || (x2 - x1) < 45.0;
1222
1223    let mid_x = (x1 + x2) / 2.0;
1224    if simple_path {
1225        write!(output, "    {}",
1226            path().move_to(x1, y1)
1227                .cubic_bezier_to(mid_x, y1, mid_x, y2, x2, y2)
1228                .fill(Fill::None)
1229                .stroke(Stroke::Color(rgb(100, 100, 100), 3.0))
1230        ).unwrap();
1231    } else {
1232        let ctrl1_x = (mid_x + x1) / 2.0;
1233        let ctrl2_x = (mid_x + x2) / 2.0;
1234        let ctrl_y = y1 - 25.0;
1235        write!(output, "    {}",
1236            path().move_to(x1, y1)
1237                .cubic_bezier_to(ctrl1_x, y1, ctrl1_x, ctrl_y, mid_x, ctrl_y)
1238                .cubic_bezier_to(ctrl2_x, ctrl_y, ctrl2_x, y2, x2, y2)
1239                .fill(Fill::None)
1240                .stroke(Stroke::Color(rgb(100, 100, 100), 3.0))
1241        ).unwrap();
1242    }
1243}
1244
1245/// Construct a picture cache render task location for testing
1246#[cfg(test)]
1247fn pc_target(
1248    surface_id: u64,
1249    tile_x: i32,
1250    tile_y: i32,
1251) -> RenderTaskLocation {
1252    use crate::{
1253        composite::{NativeSurfaceId, NativeTileId},
1254        picture::ResolvedSurfaceTexture,
1255    };
1256
1257    let width = 512;
1258    let height = 512;
1259
1260    RenderTaskLocation::Static {
1261        surface: StaticRenderTaskSurface::PictureCache {
1262            surface: ResolvedSurfaceTexture::Native {
1263                id: NativeTileId {
1264                    surface_id: NativeSurfaceId(surface_id),
1265                    x: tile_x,
1266                    y: tile_y,
1267                },
1268                size: DeviceIntSize::new(width, height),
1269            },
1270        },
1271        rect: DeviceIntSize::new(width, height).into(),
1272    }
1273}
1274
1275#[cfg(test)]
1276impl RenderTaskGraphBuilder {
1277    fn test_expect(
1278        mut self,
1279        pass_count: usize,
1280        total_surface_count: usize,
1281        unique_surfaces: &[(i32, i32, ImageFormat)],
1282    ) {
1283        use crate::{internal_types::FrameStamp, renderer::{GpuBufferBuilderF, GpuBufferBuilderI}};
1284        use api::{DocumentId, IdNamespace};
1285
1286        let mut rc = ResourceCache::new_for_testing();
1287
1288        let mut frame_stamp = FrameStamp::first(DocumentId::new(IdNamespace(1), 1));
1289        frame_stamp.advance();
1290
1291        let frame_memory = FrameMemory::fallback();
1292        let mut gpu_buffers = GpuBufferBuilder {
1293            f32: GpuBufferBuilderF::new(&frame_memory, 0, FrameId::first()),
1294            i32: GpuBufferBuilderI::new(&frame_memory, 0, FrameId::first()),
1295        };
1296        let g = self.end_frame(&mut rc, &mut gpu_buffers, &mut frame_memory.new_vec(), 2048, &frame_memory);
1297        g.print();
1298
1299        assert_eq!(g.passes.len(), pass_count);
1300        assert_eq!(g.surface_counts(), (total_surface_count, unique_surfaces.len()));
1301
1302        rc.validate_surfaces(unique_surfaces);
1303    }
1304}
1305
1306/// Construct a testing render task with given location
1307#[cfg(test)]
1308fn task_location(location: RenderTaskLocation) -> RenderTask {
1309    RenderTask::new_test(
1310        location,
1311        RenderTargetKind::Color,
1312    )
1313}
1314
1315/// Construct a dynamic render task location for testing
1316#[cfg(test)]
1317fn task_dynamic(size: i32) -> RenderTask {
1318    RenderTask::new_test(
1319        RenderTaskLocation::Unallocated { size: DeviceIntSize::new(size, size) },
1320        RenderTargetKind::Color,
1321    )
1322}
1323
1324#[test]
1325fn fg_test_1() {
1326    // Test that a root target can be used as an input for readbacks
1327    // This functionality isn't currently used, but will be in future.
1328
1329    let mut gb = RenderTaskGraphBuilder::new();
1330
1331    let root_target = pc_target(0, 0, 0);
1332
1333    let root = gb.add().init(task_location(root_target.clone()));
1334
1335    let readback = gb.add().init(task_dynamic(100));
1336    gb.add_dependency(readback, root);
1337
1338    let mix_blend_content = gb.add().init(task_dynamic(50));
1339
1340    let content = gb.add().init(task_location(root_target));
1341    gb.add_dependency(content, readback);
1342    gb.add_dependency(content, mix_blend_content);
1343
1344    gb.test_expect(3, 1, &[
1345        (2048, 2048, ImageFormat::RGBA8),
1346    ]);
1347}
1348
1349#[test]
1350fn fg_test_3() {
1351    // Test that small targets are allocated in a shared surface, and that large
1352    // tasks are allocated in a rounded up texture size.
1353
1354    let mut gb = RenderTaskGraphBuilder::new();
1355
1356    let pc_root = gb.add().init(task_location(pc_target(0, 0, 0)));
1357
1358    let child_pic_0 = gb.add().init(task_dynamic(128));
1359    let child_pic_1 = gb.add().init(task_dynamic(3000));
1360
1361    gb.add_dependency(pc_root, child_pic_0);
1362    gb.add_dependency(pc_root, child_pic_1);
1363
1364    gb.test_expect(2, 2, &[
1365        (2048, 2048, ImageFormat::RGBA8),
1366        (3072, 3072, ImageFormat::RGBA8),
1367    ]);
1368}
1369
1370#[test]
1371fn fg_test_4() {
1372    // Test that for a simple dependency chain of tasks, that render
1373    // target surfaces are aliased and reused between passes where possible.
1374
1375    let mut gb = RenderTaskGraphBuilder::new();
1376
1377    let pc_root = gb.add().init(task_location(pc_target(0, 0, 0)));
1378
1379    let child_pic_0 = gb.add().init(task_dynamic(128));
1380    let child_pic_1 = gb.add().init(task_dynamic(128));
1381    let child_pic_2 = gb.add().init(task_dynamic(128));
1382
1383    gb.add_dependency(pc_root, child_pic_0);
1384    gb.add_dependency(child_pic_0, child_pic_1);
1385    gb.add_dependency(child_pic_1, child_pic_2);
1386
1387    gb.test_expect(4, 3, &[
1388        (2048, 2048, ImageFormat::RGBA8),
1389        (2048, 2048, ImageFormat::RGBA8),
1390    ]);
1391}
1392
1393#[test]
1394fn fg_test_5() {
1395    // Test that a task that is used as an input by direct parent and also
1396    // distance ancestor are scheduled correctly, and allocates the correct
1397    // number of passes, taking advantage of surface reuse / aliasing where feasible.
1398
1399    let mut gb = RenderTaskGraphBuilder::new();
1400
1401    let pc_root = gb.add().init(task_location(pc_target(0, 0, 0)));
1402
1403    let child_pic_0 = gb.add().init(task_dynamic(128));
1404    let child_pic_1 = gb.add().init(task_dynamic(64));
1405    let child_pic_2 = gb.add().init(task_dynamic(32));
1406    let child_pic_3 = gb.add().init(task_dynamic(16));
1407
1408    gb.add_dependency(pc_root, child_pic_0);
1409    gb.add_dependency(child_pic_0, child_pic_1);
1410    gb.add_dependency(child_pic_1, child_pic_2);
1411    gb.add_dependency(child_pic_2, child_pic_3);
1412    gb.add_dependency(pc_root, child_pic_3);
1413
1414    gb.test_expect(5, 4, &[
1415        (2048, 2048, ImageFormat::RGBA8),
1416        (2048, 2048, ImageFormat::RGBA8),
1417        (2048, 2048, ImageFormat::RGBA8),
1418    ]);
1419}
1420
1421#[test]
1422fn fg_test_6() {
1423    // Test that a task that is used as an input dependency by two parent
1424    // tasks is correctly allocated and freed.
1425
1426    let mut gb = RenderTaskGraphBuilder::new();
1427
1428    let pc_root_1 = gb.add().init(task_location(pc_target(0, 0, 0)));
1429    let pc_root_2 = gb.add().init(task_location(pc_target(0, 1, 0)));
1430
1431    let child_pic = gb.add().init(task_dynamic(128));
1432
1433    gb.add_dependency(pc_root_1, child_pic);
1434    gb.add_dependency(pc_root_2, child_pic);
1435
1436    gb.test_expect(2, 1, &[
1437        (2048, 2048, ImageFormat::RGBA8),
1438    ]);
1439}
1440
1441#[test]
1442fn fg_test_7() {
1443    // Test that a standalone surface is not incorrectly used to
1444    // allocate subsequent shared task rects.
1445
1446    let mut gb = RenderTaskGraphBuilder::new();
1447
1448    let pc_root = gb.add().init(task_location(pc_target(0, 0, 0)));
1449
1450    let child0 = gb.add().init(task_dynamic(16));
1451    let child1 = gb.add().init(task_dynamic(16));
1452
1453    let child2 = gb.add().init(task_dynamic(16));
1454    let child3 = gb.add().init(task_dynamic(16));
1455
1456    gb.add_dependency(pc_root, child0);
1457    gb.add_dependency(child0, child1);
1458    gb.add_dependency(pc_root, child1);
1459
1460    gb.add_dependency(pc_root, child2);
1461    gb.add_dependency(child2, child3);
1462
1463    gb.test_expect(3, 3, &[
1464        (2048, 2048, ImageFormat::RGBA8),
1465        (2048, 2048, ImageFormat::RGBA8),
1466        (2048, 2048, ImageFormat::RGBA8),
1467    ]);
1468}