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