1use 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
34const TEXTURE_DIMENSION_MASK: i32 = 0xFF;
38
39pub 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#[cfg_attr(feature = "capture", derive(Serialize))]
86#[cfg_attr(feature = "replay", derive(Deserialize))]
87struct Surface {
88 kind: RenderTargetKind,
90 allocator: GuillotineAllocator,
92 is_shared: bool,
94 free_after: PassId,
97}
98
99impl Surface {
100 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#[cfg_attr(feature = "capture", derive(Serialize))]
122#[cfg_attr(feature = "replay", derive(Deserialize))]
123#[derive(Debug)]
124pub enum SubPassSurface {
125 Dynamic {
127 texture_id: CacheTextureId,
129 target_kind: RenderTargetKind,
131 used_rect: DeviceIntRect,
134 },
135 Persistent {
136 surface: StaticRenderTaskSurface,
138 },
139}
140
141#[cfg_attr(feature = "capture", derive(Serialize))]
143#[cfg_attr(feature = "replay", derive(Deserialize))]
144pub struct SubPass {
145 pub surface: SubPassSurface,
147 pub task_ids: FrameVec<RenderTaskId>,
149}
150
151#[cfg_attr(feature = "capture", derive(Serialize))]
154#[cfg_attr(feature = "replay", derive(Deserialize))]
155pub struct Pass {
156 pub task_ids: FrameVec<RenderTaskId>,
158 pub sub_passes: FrameVec<SubPass>,
160 pub textures_to_invalidate: FrameVec<CacheTextureId>,
163}
164
165#[cfg_attr(feature = "capture", derive(Serialize))]
168#[cfg_attr(feature = "replay", derive(Deserialize))]
169pub struct RenderTaskGraph {
170 pub tasks: FrameVec<RenderTask>,
172
173 pub passes: FrameVec<Pass>,
175
176 frame_id: FrameId,
178
179 pub task_data: FrameVec<RenderTaskData>,
181
182 #[cfg(test)]
184 surface_count: usize,
185
186 #[cfg(test)]
188 unique_surfaces: FastHashSet<CacheTextureId>,
189}
190
191pub struct RenderTaskGraphBuilder {
194 tasks: Vec<RenderTask>,
196
197 roots: FastHashSet<RenderTaskId>,
199
200 frame_id: FrameId,
202
203 textures_to_free: FastHashSet<CacheTextureId>,
206
207 active_surfaces: FastHashMap<CacheTextureId, Surface>,
210}
211
212impl RenderTaskGraphBuilder {
213 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 pub fn begin_frame(&mut self, frame_id: FrameId) {
231 self.frame_id = frame_id;
232 self.roots.clear();
233 }
234
235 pub fn get_task(
239 &self,
240 task_id: RenderTaskId,
241 ) -> &RenderTask {
242 &self.tasks[task_id.index as usize]
243 }
244
245 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 pub fn add(&mut self) -> RenderTaskAllocation {
257 self.roots.insert(
259 RenderTaskId { index: self.tasks.len() as u32 }
260 );
261
262 RenderTaskAllocation {
263 alloc: self.tasks.alloc(),
264 }
265 }
266
267 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 self.roots.remove(&input);
277 }
278
279 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 let task_count = self.tasks.len();
290
291 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 let mut pass_count = 0;
320 let mut passes = memory.new_vec();
321 let mut task_sorter = TopologicalSort::<RenderTaskId>::new();
322
323 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 loop {
337 let tasks = task_sorter.pop_all();
339
340 if tasks.is_empty() {
342 assert!(task_sorter.is_empty());
345 break;
346 } else {
347 for task_id in &tasks {
349 graph.tasks[task_id.index as usize].render_on = PassId(pass_count);
350 }
351
352 passes.push(tasks);
354 pass_count += 1;
355 }
356 }
357
358 pass_count = pass_count.max(1);
360
361 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 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 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 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 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 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 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 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 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 let texture_id = resource_cache.get_or_create_render_target_from_pool(
471 surface_size,
472 format,
473 );
474
475 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 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 let _prev_surface = self.active_surfaces.insert(texture_id, surface);
496 assert!(_prev_surface.is_none());
497
498 #[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 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 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 pass.sub_passes.push(SubPass {
543 surface: SubPassSurface::Dynamic {
544 texture_id,
545 target_kind: kind,
546 used_rect: rect, },
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 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 }
574 RenderTaskLocation::Dynamic { .. } => {
575 panic!("bug: encountered an already allocated task");
577 }
578 }
579
580 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 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 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 assert!(self.active_surfaces.is_empty());
614 }
615
616 for task in &mut graph.tasks {
621 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 None
640 };
641
642 if let Some(cache_item) = cache_item {
643 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 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 #[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 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 #[cfg(test)]
784 pub fn surface_counts(&self) -> (usize, usize) {
785 (self.surface_count, self.unique_surfaces.len())
786 }
787
788 #[cfg(debug_assertions)]
790 pub fn frame_id(&self) -> FrameId {
791 self.frame_id
792 }
793}
794
795impl 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 match child_location {
821 RenderTaskLocation::CacheRequest { .. } => {}
822 RenderTaskLocation::Static { .. } => {
823 }
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#[cfg_attr(feature = "capture", derive(Serialize))]
856#[cfg_attr(feature = "replay", derive(Deserialize))]
857pub struct RenderPass {
858 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 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#[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 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 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 for (x1, y1, x2, y2) in links {
979 dump_task_dependency_link(output, x1, y1, x2, y2);
980 }
981
982 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 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#[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#[cfg(test)]
1122fn task_location(location: RenderTaskLocation) -> RenderTask {
1123 RenderTask::new_test(
1124 location,
1125 RenderTargetKind::Color,
1126 )
1127}
1128
1129#[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 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 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 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 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 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 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}