freya_core/render/
pipeline.rs

1use freya_engine::prelude::{
2    ClipOp,
3    Color,
4    FontCollection,
5    FontMgr,
6    Matrix,
7    Point,
8    Rect,
9    SamplingOptions,
10    Surface,
11};
12use freya_native_core::{
13    node::{
14        ElementNode,
15        NodeType,
16    },
17    real_dom::NodeImmutable,
18    tags::TagName,
19    NodeId,
20};
21use itertools::sorted;
22use torin::prelude::{
23    Area,
24    LayoutNode,
25    Torin,
26};
27
28use super::{
29    wireframe_renderer,
30    Compositor,
31    CompositorCache,
32    CompositorDirtyArea,
33};
34use crate::{
35    dom::{
36        CompositorDirtyNodes,
37        DioxusDOM,
38        DioxusNode,
39        ImagesCache,
40    },
41    elements::{
42        ElementUtils,
43        ElementUtilsResolver,
44    },
45    layers::Layers,
46    states::{
47        TransformState,
48        ViewportState,
49    },
50};
51
52/// Runs the full rendering cycle.
53pub struct RenderPipeline<'a> {
54    pub rdom: &'a DioxusDOM,
55    pub layers: &'a Layers,
56    pub layout: &'a Torin<NodeId>,
57    pub compositor_dirty_nodes: &'a mut CompositorDirtyNodes,
58    pub compositor_dirty_area: &'a mut CompositorDirtyArea,
59    pub compositor_cache: &'a mut CompositorCache,
60    pub surface: &'a mut Surface,
61    pub dirty_surface: &'a mut Surface,
62    pub compositor: &'a mut Compositor,
63    pub font_collection: &'a mut FontCollection,
64    pub font_manager: &'a FontMgr,
65    pub images_cache: &'a mut ImagesCache,
66    pub canvas_area: Area,
67    pub background: Color,
68    pub scale_factor: f32,
69    pub selected_node: Option<NodeId>,
70    pub default_fonts: &'a [String],
71}
72
73impl RenderPipeline<'_> {
74    pub fn run(&mut self) {
75        let mut dirty_layers = Layers::default();
76
77        // Process what nodes need to be rendered
78        let rendering_layers = self.compositor.run(
79            self.compositor_dirty_nodes,
80            self.compositor_dirty_area,
81            self.compositor_cache,
82            self.layers,
83            &mut dirty_layers,
84            self.layout,
85            self.rdom,
86            self.scale_factor,
87        );
88
89        #[cfg(feature = "fade-cached-incremental-areas")]
90        {
91            // Slowly fade into white non-rerendered areas
92            if self.compositor_dirty_area.is_some() {
93                use freya_engine::prelude::{
94                    Paint,
95                    PaintStyle,
96                };
97                let rect = Rect::new(
98                    self.canvas_area.min_x(),
99                    self.canvas_area.min_y(),
100                    self.canvas_area.max_x(),
101                    self.canvas_area.max_y(),
102                );
103                let mut paint = Paint::default();
104                paint.set_color(Color::from_argb(10, 245, 245, 245));
105                paint.set_anti_alias(true);
106                paint.set_style(PaintStyle::Fill);
107                self.dirty_surface.canvas().draw_rect(rect, &paint);
108            }
109        }
110
111        self.dirty_surface.canvas().save();
112
113        // Round the area out to prevent float pixels issues
114        self.compositor_dirty_area.round_out();
115
116        // Clear using the the background only, but only the dirty
117        // area in which it will render the intersected nodes again
118        if let Some(dirty_area) = self.compositor_dirty_area.take() {
119            #[cfg(debug_assertions)]
120            tracing::info!("Marked {dirty_area:?} as dirty area");
121
122            self.dirty_surface.canvas().clip_rect(
123                Rect::new(
124                    dirty_area.min_x(),
125                    dirty_area.min_y(),
126                    dirty_area.max_x(),
127                    dirty_area.max_y(),
128                ),
129                ClipOp::Intersect,
130                false,
131            );
132            self.dirty_surface.canvas().clear(self.background);
133        }
134
135        #[cfg(debug_assertions)]
136        // Counter of painted nodes for debugging purposes
137        let mut painted = 0;
138
139        // Render the dirty nodes
140        for (_, nodes) in sorted(rendering_layers.iter()) {
141            'elements: for node_id in sorted(nodes) {
142                let node_ref = self.rdom.get(*node_id).unwrap();
143                let node_viewports = node_ref.get::<ViewportState>().unwrap();
144                let layout_node = self.layout.get(*node_id);
145
146                if let Some(layout_node) = layout_node {
147                    // Skip elements that are completely out of any their parent's viewport
148                    for viewport_id in &node_viewports.viewports {
149                        let viewport = self.layout.get(*viewport_id).unwrap().visible_area();
150                        if !viewport.intersects(&layout_node.area) {
151                            continue 'elements;
152                        }
153                    }
154
155                    // Render the element
156                    self.render(node_ref, layout_node);
157
158                    #[cfg(debug_assertions)]
159                    {
160                        painted += 1;
161                    }
162                }
163            }
164        }
165
166        if let Some(selected_node) = &self.selected_node {
167            if let Some(layout_node) = self.layout.get(*selected_node) {
168                wireframe_renderer::render_wireframe(
169                    self.dirty_surface.canvas(),
170                    &layout_node.visible_area(),
171                );
172            }
173        }
174
175        #[cfg(debug_assertions)]
176        {
177            if painted > 0 {
178                tracing::info!("Painted {painted} nodes");
179            }
180        }
181
182        // Copy the dirty canvas into the main canvas
183        self.dirty_surface.canvas().restore();
184        self.surface.canvas().clear(self.background);
185        self.dirty_surface.draw(
186            self.surface.canvas(),
187            (0, 0),
188            SamplingOptions::default(),
189            None,
190        );
191
192        self.compositor_dirty_nodes.clear();
193    }
194
195    pub fn render(&mut self, node_ref: DioxusNode, layout_node: &LayoutNode) {
196        let dirty_canvas = self.dirty_surface.canvas();
197        let node_type = &*node_ref.node_type();
198        if let NodeType::Element(ElementNode { tag, .. }) = node_type {
199            let Some(element_utils) = tag.utils() else {
200                return;
201            };
202
203            let initial_layer = dirty_canvas.save();
204
205            let node_transform = &*node_ref.get::<TransformState>().unwrap();
206            let node_viewports = node_ref.get::<ViewportState>().unwrap();
207
208            for node_id in &node_viewports.viewports {
209                let node_ref = self.rdom.get(*node_id).unwrap();
210                let node_type = node_ref.node_type();
211                let Some(element_utils) = node_type.tag().and_then(|tag| tag.utils()) else {
212                    continue;
213                };
214                let layout_node = self.layout.get(*node_id).unwrap();
215                element_utils.clip(layout_node, &node_ref, dirty_canvas, self.scale_factor);
216            }
217
218            // Apply inherited scale effects
219            for (id, scale_x, scale_y) in &node_transform.scales {
220                let layout_node = self.layout.get(*id).unwrap();
221                let area = layout_node.visible_area();
222                let center = area.center();
223                dirty_canvas.translate((center.x, center.y));
224                dirty_canvas.scale((*scale_x, *scale_y));
225                dirty_canvas.translate((-center.x, -center.y));
226            }
227
228            // Pass rotate effect to children
229            for (id, rotate_degs) in &node_transform.rotations {
230                let layout_node = self.layout.get(*id).unwrap();
231                let area = layout_node.visible_area();
232                let mut matrix = Matrix::new_identity();
233                matrix.set_rotate(
234                    *rotate_degs,
235                    Some(Point {
236                        x: area.min_x() + area.width() / 2.0,
237                        y: area.min_y() + area.height() / 2.0,
238                    }),
239                );
240                dirty_canvas.concat(&matrix);
241            }
242
243            // Apply inherited opacity effects
244            for opacity in &node_transform.opacities {
245                dirty_canvas.save_layer_alpha_f(
246                    Rect::new(
247                        self.canvas_area.min_x(),
248                        self.canvas_area.min_y(),
249                        self.canvas_area.max_x(),
250                        self.canvas_area.max_y(),
251                    ),
252                    *opacity,
253                );
254            }
255
256            // Clip the element itself if non-children content can overflow, like an image in case of `image`
257            // or text in the case of `label` or `paragraph`
258            if *tag == TagName::Paragraph || *tag == TagName::Label || *tag == TagName::Image {
259                element_utils.clip(layout_node, &node_ref, dirty_canvas, self.scale_factor);
260            }
261
262            element_utils.render(
263                layout_node,
264                &node_ref,
265                dirty_canvas,
266                self.font_collection,
267                self.font_manager,
268                self.default_fonts,
269                self.images_cache,
270                self.scale_factor,
271            );
272
273            dirty_canvas.restore_to_count(initial_layer);
274        }
275    }
276}