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 layout_node = self.tree.layout.get(node_id).unwrap();
41
42                if layout_node.hidden {
43                    continue 'rendering;
44                }
45
46                let layer = self.canvas.save();
47
48                let element = self.tree.elements.get(node_id).unwrap();
49                let text_style_state = self.tree.text_style_state.get(node_id).unwrap();
50                let effect_state = self.tree.effect_state.get(node_id);
51
52                if let Some(effect_state) = effect_state {
53                    let mut visible_area = layout_node.visible_area();
54
55                    // Transform the element area given the scale effects
56                    for id in effect_state.scales.iter() {
57                        let layout_node = self.tree.layout.get(id).unwrap();
58                        let effect = self.tree.effect_state.get(id).unwrap();
59                        let area = layout_node.visible_area();
60                        let origin = effect.transform_origin.origin(&area);
61                        let scale = effect.scale.unwrap();
62
63                        visible_area = visible_area.translate(-origin.to_vector());
64                        visible_area = visible_area.scale(scale.x, scale.y);
65                        visible_area = visible_area.translate(origin.to_vector());
66                    }
67
68                    hotpath::measure_block!("Element Clipping", {
69                        for clip_node_id in effect_state.clips.iter() {
70                            let clip_element = self.tree.elements.get(clip_node_id).unwrap();
71                            let clip_layout_node = self.tree.layout.get(clip_node_id).unwrap();
72                            let clip_effect = self.tree.effect_state.get(clip_node_id).unwrap();
73
74                            let mut transformed_clip_area = clip_layout_node.visible_area();
75
76                            // For every clip area that his element gets we also need to apply the effects to each one so that
77                            // we can properly assume whether this element is actually visible or not
78                            for id in clip_effect.scales.iter() {
79                                let scale_layout_node = self.tree.layout.get(id).unwrap();
80                                let scale_effect = self.tree.effect_state.get(id).unwrap();
81                                let area = scale_layout_node.visible_area();
82                                let origin = scale_effect.transform_origin.origin(&area);
83                                let scale = scale_effect.scale.unwrap();
84
85                                transformed_clip_area =
86                                    transformed_clip_area.translate(-origin.to_vector());
87                                transformed_clip_area =
88                                    transformed_clip_area.scale(scale.x, scale.y);
89                                transformed_clip_area =
90                                    transformed_clip_area.translate(origin.to_vector());
91                            }
92
93                            // No need to render this element as it is completely clipped
94                            if !visible_area.intersects(&transformed_clip_area) {
95                                self.canvas.restore_to_count(layer);
96                                continue 'rendering;
97                            }
98
99                            let clip_context = ClipContext {
100                                canvas: self.canvas,
101                                visible_area: &transformed_clip_area,
102                                scale_factor: self.scale_factor,
103                            };
104
105                            clip_element.clip(clip_context);
106                        }
107                    });
108
109                    // Pass rotate effect to children
110                    for id in effect_state.rotations.iter() {
111                        let layout_node = self.tree.layout.get(id).unwrap();
112                        let effect = self.tree.effect_state.get(id).unwrap();
113                        let area = layout_node.visible_area();
114                        let origin = effect.transform_origin.origin(&area);
115                        let mut matrix = SkMatrix::new_identity();
116                        matrix.set_rotate(
117                            effect.rotation.unwrap(),
118                            Some(SkPoint {
119                                x: origin.x,
120                                y: origin.y,
121                            }),
122                        );
123                        self.canvas.concat(&matrix);
124                    }
125
126                    let render_rect = element.render_rect(&visible_area, self.scale_factor as f32);
127
128                    // Apply inherited opacity effects with bounds expanded
129                    // to accommodate outset shadows
130                    let mut layer_bounds = *render_rect.rect();
131                    let scale_factor = self.scale_factor as f32;
132
133                    for shadow in element.style().shadows.iter() {
134                        if shadow.position == ShadowPosition::Normal {
135                            let outset_x = shadow.x.abs() + shadow.spread + shadow.blur;
136                            let outset_y = shadow.y.abs() + shadow.spread + shadow.blur;
137                            layer_bounds = layer_bounds
138                                .with_outset((outset_x * scale_factor, outset_y * scale_factor));
139                        }
140                    }
141
142                    for opacity in effect_state.opacities.iter() {
143                        self.canvas.save_layer_alpha_f(layer_bounds, *opacity);
144                    }
145
146                    // Transform the canvas area given the scale effects
147                    for id in effect_state.scales.iter() {
148                        let layout_node = self.tree.layout.get(id).unwrap();
149                        let effect = self.tree.effect_state.get(id).unwrap();
150                        let area = layout_node.visible_area();
151                        let origin = effect.transform_origin.origin(&area);
152                        let scale = effect.scale.unwrap();
153
154                        self.canvas.translate((origin.x, origin.y));
155                        self.canvas.scale((scale.x, scale.y));
156                        self.canvas.translate((-origin.x, -origin.y));
157                    }
158                }
159
160                let render_context = RenderContext {
161                    font_collection: self.font_collection,
162                    canvas: self.canvas,
163                    layout_node,
164                    tree: self.tree,
165                    text_style_state,
166                    scale_factor: self.scale_factor,
167                };
168
169                hotpath::measure_block!("Element Render", {
170                    element.render(render_context);
171                });
172
173                if let Some(effect_state) = effect_state {
174                    let visible_area = layout_node.visible_area();
175                    let render_rect = element.render_rect(&visible_area, self.scale_factor as f32);
176                    // Apply blur effect
177                    if let Some(blur_radius) = effect_state.blur {
178                        let style = element.style();
179
180                        let image_filter = blur(
181                            (
182                                blur_radius * self.scale_factor as f32,
183                                blur_radius * self.scale_factor as f32,
184                            ),
185                            None,
186                            None,
187                            render_rect.rect(),
188                        );
189                        if let Some(image_filter) = image_filter {
190                            let rec = SaveLayerRec::default()
191                                .bounds(render_rect.rect())
192                                .backdrop(&image_filter);
193                            if style.corner_radius.is_round() {
194                                self.canvas.clip_rrect(render_rect, ClipOp::Intersect, true);
195                                self.canvas.save_layer(&rec);
196                            } else {
197                                self.canvas.save_layer(&rec);
198                            }
199                        }
200                    }
201                }
202
203                self.canvas.restore_to_count(layer);
204            }
205        }
206    }
207}