Skip to main content

freya_core/
render_pipeline.rs

1use freya_engine::prelude::{
2    Canvas,
3    ClipOp,
4    FontCollection,
5    FontMgr,
6    SaveLayerRec,
7    SkMatrix,
8    SkPoint,
9    blur,
10};
11
12use crate::{
13    element::{
14        ClipContext,
15        RenderContext,
16    },
17    prelude::Color,
18    style::shadow::ShadowPosition,
19    tree::Tree,
20};
21
22pub struct RenderPipeline<'a> {
23    pub font_collection: &'a mut FontCollection,
24    pub font_manager: &'a FontMgr,
25    pub canvas: &'a Canvas,
26    pub tree: &'a Tree,
27    pub scale_factor: f64,
28    pub background: Color,
29}
30
31impl RenderPipeline<'_> {
32    #[cfg_attr(feature = "hotpath", hotpath::measure)]
33    pub fn render(self) {
34        self.canvas.clear(self.background);
35
36        // TODO: Use incremental rendering
37        for i16 in itertools::sorted(self.tree.layers.keys()) {
38            let nodes = self.tree.layers.get(i16).unwrap();
39            'rendering: for node_id in nodes {
40                let layer = self.canvas.save();
41
42                let element = self.tree.elements.get(node_id).unwrap();
43                let text_style_state = self.tree.text_style_state.get(node_id).unwrap();
44                let layout_node = self.tree.layout.get(node_id).unwrap();
45                let effect_state = self.tree.effect_state.get(node_id);
46
47                if let Some(effect_state) = effect_state {
48                    let mut visible_area = layout_node.visible_area();
49
50                    // Transform the element area given the scale effects
51                    for id in effect_state.scales.iter() {
52                        let layout_node = self.tree.layout.get(id).unwrap();
53                        let effect = self.tree.effect_state.get(id).unwrap();
54                        let area = layout_node.visible_area();
55                        let center = area.center();
56                        let scale = effect.scale.unwrap();
57
58                        visible_area = visible_area.translate(-center.to_vector());
59                        visible_area = visible_area.scale(scale.x, scale.y);
60                        visible_area = visible_area.translate(center.to_vector());
61                    }
62
63                    hotpath::measure_block!("Element Clipping", {
64                        for clip_node_id in effect_state.clips.iter() {
65                            let clip_element = self.tree.elements.get(clip_node_id).unwrap();
66                            let clip_layout_node = self.tree.layout.get(clip_node_id).unwrap();
67                            let clip_effect = self.tree.effect_state.get(clip_node_id).unwrap();
68
69                            let mut transformed_clip_area = clip_layout_node.visible_area();
70
71                            // For every clip area that his element gets we also need to apply the effects to each one so that
72                            // we can properly assume whether this element is actually visible or not
73                            for id in clip_effect.scales.iter() {
74                                let scale_layout_node = self.tree.layout.get(id).unwrap();
75                                let scale_effect = self.tree.effect_state.get(id).unwrap();
76                                let area = scale_layout_node.visible_area();
77                                let center = area.center();
78                                let scale = scale_effect.scale.unwrap();
79
80                                transformed_clip_area =
81                                    transformed_clip_area.translate(-center.to_vector());
82                                transformed_clip_area =
83                                    transformed_clip_area.scale(scale.x, scale.y);
84                                transformed_clip_area =
85                                    transformed_clip_area.translate(center.to_vector());
86                            }
87
88                            // No need to render this element as it is completely clipped
89                            if !visible_area.intersects(&transformed_clip_area) {
90                                self.canvas.restore_to_count(layer);
91                                continue 'rendering;
92                            }
93
94                            let clip_context = ClipContext {
95                                canvas: self.canvas,
96                                visible_area: &transformed_clip_area,
97                                scale_factor: self.scale_factor,
98                            };
99
100                            clip_element.clip(clip_context);
101                        }
102                    });
103
104                    // Pass rotate effect to children
105                    for id in effect_state.rotations.iter() {
106                        let layout_node = self.tree.layout.get(id).unwrap();
107                        let effect = self.tree.effect_state.get(id).unwrap();
108                        let area = layout_node.visible_area();
109                        let mut matrix = SkMatrix::new_identity();
110                        matrix.set_rotate(
111                            effect.rotation.unwrap(),
112                            Some(SkPoint {
113                                x: area.min_x() + area.width() / 2.0,
114                                y: area.min_y() + area.height() / 2.0,
115                            }),
116                        );
117                        self.canvas.concat(&matrix);
118                    }
119
120                    let render_rect = element.render_rect(&visible_area, self.scale_factor as f32);
121
122                    // Apply inherited opacity effects with bounds expanded
123                    // to accommodate outset shadows
124                    let mut layer_bounds = *render_rect.rect();
125                    let scale_factor = self.scale_factor as f32;
126
127                    for shadow in element.style().shadows.iter() {
128                        if shadow.position == ShadowPosition::Normal {
129                            let outset_x = shadow.x.abs() + shadow.spread + shadow.blur;
130                            let outset_y = shadow.y.abs() + shadow.spread + shadow.blur;
131                            layer_bounds = layer_bounds
132                                .with_outset((outset_x * scale_factor, outset_y * scale_factor));
133                        }
134                    }
135
136                    for opacity in effect_state.opacities.iter() {
137                        self.canvas.save_layer_alpha_f(layer_bounds, *opacity);
138                    }
139
140                    // Transform the canvas area given the scale effects
141                    for id in effect_state.scales.iter() {
142                        let layout_node = self.tree.layout.get(id).unwrap();
143                        let effect = self.tree.effect_state.get(id).unwrap();
144                        let area = layout_node.visible_area();
145                        let center = area.center();
146                        let scale = effect.scale.unwrap();
147
148                        self.canvas.translate((center.x, center.y));
149                        self.canvas.scale((scale.x, scale.y));
150                        self.canvas.translate((-center.x, -center.y));
151                    }
152                }
153
154                let render_context = RenderContext {
155                    font_collection: self.font_collection,
156                    canvas: self.canvas,
157                    layout_node,
158                    tree: self.tree,
159                    text_style_state,
160                    scale_factor: self.scale_factor,
161                };
162
163                hotpath::measure_block!("Element Render", {
164                    element.render(render_context);
165                });
166
167                if let Some(effect_state) = effect_state {
168                    let visible_area = layout_node.visible_area();
169                    let render_rect = element.render_rect(&visible_area, self.scale_factor as f32);
170                    // Apply blur effect
171                    if let Some(blur_radius) = effect_state.blur {
172                        let style = element.style();
173
174                        let image_filter = blur(
175                            (
176                                blur_radius * self.scale_factor as f32,
177                                blur_radius * self.scale_factor as f32,
178                            ),
179                            None,
180                            None,
181                            render_rect.rect(),
182                        );
183                        if let Some(image_filter) = image_filter {
184                            let rec = SaveLayerRec::default()
185                                .bounds(render_rect.rect())
186                                .backdrop(&image_filter);
187                            if style.corner_radius.is_round() {
188                                self.canvas.clip_rrect(render_rect, ClipOp::Intersect, true);
189                                self.canvas.save_layer(&rec);
190                            } else {
191                                self.canvas.save_layer(&rec);
192                            }
193                        }
194                    }
195                }
196
197                self.canvas.restore_to_count(layer);
198            }
199        }
200    }
201}