1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4#![warn(missing_docs)]
5//! module for rendering the tree of items
6
7use super::graphics::RenderingCache;
8use super::items::*;
9use crate::graphics::{CachedGraphicsData, FontRequest, Image, IntRect};
10use crate::item_tree::ItemTreeRc;
11use crate::item_tree::{ItemVisitor, ItemVisitorResult, ItemVisitorVTable, VisitChildrenResult};
12use crate::lengths::{
13 ItemTransform, LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalPx, LogicalRect,
14 LogicalSize, LogicalVector, SizeLengths,
15};
16use crate::properties::PropertyTracker;
17use crate::window::{WindowAdapter, WindowInner};
18use crate::{Brush, Coord, SharedString};
19use alloc::boxed::Box;
20use alloc::rc::Rc;
21use core::cell::{Cell, RefCell};
22use core::pin::Pin;
23#[cfg(feature = "std")]
24use std::collections::HashMap;
25use vtable::VRc;
26
27/// This structure must be present in items that are Rendered and contains information.
28/// Used by the backend.
29#[derive(Default, Debug)]
30#[repr(C)]
31pub struct CachedRenderingData {
32 /// Used and modified by the backend, should be initialized to 0 by the user code
33 pub(crate) cache_index: Cell<usize>,
34 /// Used and modified by the backend, should be initialized to 0 by the user code.
35 /// The backend compares this generation against the one of the cache to verify
36 /// the validity of the cache_index field.
37 pub(crate) cache_generation: Cell<usize>,
38}
39
40impl CachedRenderingData {
41 /// This function can be used to remove an entry from the rendering cache for a given item, if it
42 /// exists, i.e. if any data was ever cached. This is typically called by the graphics backend's
43 /// implementation of the release_item_graphics_cache function.
44 pub fn release<T>(&self, cache: &mut RenderingCache<T>) -> Option<T> {
45 if self.cache_generation.get() == cache.generation() {
46 let index = self.cache_index.get();
47 self.cache_generation.set(0);
48 Some(cache.remove(index).data)
49 } else {
50 None
51 }
52 }
53
54 /// Return the value if it is in the cache
55 pub fn get_entry<'a, T>(
56 &self,
57 cache: &'a mut RenderingCache<T>,
58 ) -> Option<&'a mut crate::graphics::CachedGraphicsData<T>> {
59 let index = self.cache_index.get();
60 if self.cache_generation.get() == cache.generation() {
61 cache.get_mut(index)
62 } else {
63 None
64 }
65 }
66}
67
68/// A per-item cache.
69///
70/// Cache rendering information for a given item.
71///
72/// Use [`ItemCache::get_or_update_cache_entry`] to get or update the items, the
73/// cache is automatically invalided when the property gets dirty.
74/// [`ItemCache::component_destroyed`] must be called to clear the cache for that
75/// component.
76#[cfg(feature = "std")]
77pub struct ItemCache<T> {
78 /// The pointer is a pointer to a component
79 map: RefCell<HashMap<*const vtable::Dyn, HashMap<u32, CachedGraphicsData<T>>>>,
80 /// Track if the window scale factor changes; used to clear the cache if necessary.
81 window_scale_factor_tracker: Pin<Box<PropertyTracker>>,
82}
83
84#[cfg(feature = "std")]
85impl<T> Default for ItemCache<T> {
86 fn default() -> Self {
87 Self { map: Default::default(), window_scale_factor_tracker: Box::pin(Default::default()) }
88 }
89}
90
91#[cfg(feature = "std")]
92impl<T: Clone> ItemCache<T> {
93 /// Returns the cached value associated to the `item_rc` if it is still valid.
94 /// Otherwise call the `update_fn` to compute that value, and track property access
95 /// so it is automatically invalided when property becomes dirty.
96 pub fn get_or_update_cache_entry(&self, item_rc: &ItemRc, update_fn: impl FnOnce() -> T) -> T {
97 let component = &(**item_rc.item_tree()) as *const _;
98 let mut borrowed = self.map.borrow_mut();
99 match borrowed.entry(component).or_default().entry(item_rc.index()) {
100 std::collections::hash_map::Entry::Occupied(mut entry) => {
101 let mut tracker = entry.get_mut().dependency_tracker.take();
102 drop(borrowed);
103 let maybe_new_data = tracker
104 .get_or_insert_with(|| Box::pin(Default::default()))
105 .as_ref()
106 .evaluate_if_dirty(update_fn);
107 let mut borrowed = self.map.borrow_mut();
108 let e = borrowed.get_mut(&component).unwrap().get_mut(&item_rc.index()).unwrap();
109 e.dependency_tracker = tracker;
110 if let Some(new_data) = maybe_new_data {
111 e.data = new_data.clone();
112 new_data
113 } else {
114 e.data.clone()
115 }
116 }
117 std::collections::hash_map::Entry::Vacant(_) => {
118 drop(borrowed);
119 let new_entry = CachedGraphicsData::new(update_fn);
120 let data = new_entry.data.clone();
121 self.map
122 .borrow_mut()
123 .get_mut(&component)
124 .unwrap()
125 .insert(item_rc.index(), new_entry);
126 data
127 }
128 }
129 }
130
131 /// Returns the cached value associated with the `item_rc` if it is in the cache
132 /// and still valid.
133 pub fn with_entry<U>(
134 &self,
135 item_rc: &ItemRc,
136 callback: impl FnOnce(&T) -> Option<U>,
137 ) -> Option<U> {
138 let component = &(**item_rc.item_tree()) as *const _;
139 self.map
140 .borrow()
141 .get(&component)
142 .and_then(|per_component_entries| per_component_entries.get(&item_rc.index()))
143 .and_then(|entry| callback(&entry.data))
144 }
145
146 /// Clears the cache if the window's scale factor has changed since the last call.
147 pub fn clear_cache_if_scale_factor_changed(&self, window: &crate::api::Window) {
148 if self.window_scale_factor_tracker.is_dirty() {
149 self.window_scale_factor_tracker
150 .as_ref()
151 .evaluate_as_dependency_root(|| window.scale_factor());
152 self.clear_all();
153 }
154 }
155
156 /// free the whole cache
157 pub fn clear_all(&self) {
158 self.map.borrow_mut().clear();
159 }
160
161 /// Function that must be called when a component is destroyed.
162 ///
163 /// Usually can be called from [`crate::window::WindowAdapterInternal::unregister_item_tree`]
164 pub fn component_destroyed(&self, component: crate::item_tree::ItemTreeRef) {
165 let component_ptr: *const _ =
166 crate::item_tree::ItemTreeRef::as_ptr(component).cast().as_ptr();
167 self.map.borrow_mut().remove(&component_ptr);
168 }
169
170 /// free the cache for a given item
171 pub fn release(&self, item_rc: &ItemRc) {
172 let component = &(**item_rc.item_tree()) as *const _;
173 if let Some(sub) = self.map.borrow_mut().get_mut(&component) {
174 sub.remove(&item_rc.index());
175 }
176 }
177
178 /// Returns true if there are no entries in the cache; false otherwise.
179 pub fn is_empty(&self) -> bool {
180 self.map.borrow().is_empty()
181 }
182}
183
184/// Renders the children of the item with the specified index into the renderer.
185pub fn render_item_children(
186 renderer: &mut dyn ItemRenderer,
187 component: &ItemTreeRc,
188 index: isize,
189 window_adapter: &Rc<dyn WindowAdapter>,
190) {
191 let mut actual_visitor =
192 |component: &ItemTreeRc, index: u32, item: Pin<ItemRef>| -> VisitChildrenResult {
193 renderer.save_state();
194 let item_rc = ItemRc::new(component.clone(), index);
195
196 let (do_draw, item_geometry) = renderer.filter_item(&item_rc, window_adapter);
197
198 let item_origin = item_geometry.origin;
199 renderer.translate(item_origin.to_vector());
200
201 // Don't render items that are clipped, with the exception of the Clip or Flickable since
202 // they themselves clip their content.
203 let render_result = if do_draw
204 || item.as_ref().clips_children()
205 // HACK, the geometry of the box shadow does not include the shadow, because when the shadow is the root for repeated elements it would translate the children
206 || ItemRef::downcast_pin::<BoxShadow>(item).is_some()
207 {
208 item.as_ref().render(
209 &mut (renderer as &mut dyn ItemRenderer),
210 &item_rc,
211 item_geometry.size,
212 )
213 } else {
214 RenderingResult::ContinueRenderingChildren
215 };
216
217 if matches!(render_result, RenderingResult::ContinueRenderingChildren) {
218 render_item_children(renderer, component, index as isize, window_adapter);
219 }
220 renderer.restore_state();
221 VisitChildrenResult::CONTINUE
222 };
223 vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
224 VRc::borrow_pin(component).as_ref().visit_children_item(
225 index,
226 crate::item_tree::TraversalOrder::BackToFront,
227 actual_visitor,
228 );
229}
230
231/// Renders the tree of items that component holds, using the specified renderer. Rendering is done
232/// relative to the specified origin.
233pub fn render_component_items(
234 component: &ItemTreeRc,
235 renderer: &mut dyn ItemRenderer,
236 origin: LogicalPoint,
237 window_adapter: &Rc<dyn WindowAdapter>,
238) {
239 renderer.save_state();
240 renderer.translate(distance:origin.to_vector());
241
242 render_item_children(renderer, component, index:-1, window_adapter);
243
244 renderer.restore_state();
245}
246
247/// Compute the bounding rect of all children. This does /not/ include item's own bounding rect. Remember to run this
248/// via `evaluate_no_tracking`.
249pub fn item_children_bounding_rect(
250 component: &ItemTreeRc,
251 index: isize,
252 clip_rect: &LogicalRect,
253) -> LogicalRect {
254 let mut bounding_rect = LogicalRect::zero();
255
256 let mut actual_visitor =
257 |component: &ItemTreeRc, index: u32, item: Pin<ItemRef>| -> VisitChildrenResult {
258 let item_geometry = ItemTreeRc::borrow_pin(component).as_ref().item_geometry(index);
259
260 let local_clip_rect = clip_rect.translate(-item_geometry.origin.to_vector());
261
262 if let Some(clipped_item_geometry) = item_geometry.intersection(clip_rect) {
263 bounding_rect = bounding_rect.union(&clipped_item_geometry);
264 }
265
266 if !item.as_ref().clips_children() {
267 bounding_rect = bounding_rect.union(&item_children_bounding_rect(
268 component,
269 index as isize,
270 &local_clip_rect,
271 ));
272 }
273 VisitChildrenResult::CONTINUE
274 };
275 vtable::new_vref!(let mut actual_visitor : VRefMut<ItemVisitorVTable> for ItemVisitor = &mut actual_visitor);
276 VRc::borrow_pin(component).as_ref().visit_children_item(
277 index,
278 crate::item_tree::TraversalOrder::BackToFront,
279 actual_visitor,
280 );
281
282 bounding_rect
283}
284
285/// Trait for an item that represent a Rectangle to the Renderer
286#[allow(missing_docs)]
287pub trait RenderRectangle {
288 fn background(self: Pin<&Self>) -> Brush;
289}
290
291/// Trait for an item that represent a Rectangle with a border to the Renderer
292#[allow(missing_docs)]
293pub trait RenderBorderRectangle {
294 fn background(self: Pin<&Self>) -> Brush;
295 fn border_width(self: Pin<&Self>) -> LogicalLength;
296 fn border_radius(self: Pin<&Self>) -> LogicalBorderRadius;
297 fn border_color(self: Pin<&Self>) -> Brush;
298}
299
300/// Trait for an item that represents an Image towards the renderer
301#[allow(missing_docs)]
302pub trait RenderImage {
303 fn target_size(self: Pin<&Self>) -> LogicalSize;
304 fn source(self: Pin<&Self>) -> Image;
305 fn source_clip(self: Pin<&Self>) -> Option<IntRect>;
306 fn image_fit(self: Pin<&Self>) -> ImageFit;
307 fn rendering(self: Pin<&Self>) -> ImageRendering;
308 fn colorize(self: Pin<&Self>) -> Brush;
309 fn alignment(self: Pin<&Self>) -> (ImageHorizontalAlignment, ImageVerticalAlignment);
310 fn tiling(self: Pin<&Self>) -> (ImageTiling, ImageTiling);
311}
312
313/// Trait for an item that represents an Text towards the renderer
314#[allow(missing_docs)]
315pub trait RenderText {
316 fn target_size(self: Pin<&Self>) -> LogicalSize;
317 fn text(self: Pin<&Self>) -> SharedString;
318 fn font_request(self: Pin<&Self>, window: &WindowInner) -> FontRequest;
319 fn color(self: Pin<&Self>) -> Brush;
320 fn alignment(self: Pin<&Self>) -> (TextHorizontalAlignment, TextVerticalAlignment);
321 fn wrap(self: Pin<&Self>) -> TextWrap;
322 fn overflow(self: Pin<&Self>) -> TextOverflow;
323 fn letter_spacing(self: Pin<&Self>) -> LogicalLength;
324 fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle);
325
326 fn text_bounding_rect(
327 self: Pin<&Self>,
328 window_adapter: &Rc<dyn WindowAdapter>,
329 mut geometry: euclid::Rect<f32, crate::lengths::LogicalPx>,
330 ) -> euclid::Rect<f32, crate::lengths::LogicalPx> {
331 let window_inner = WindowInner::from_pub(window_adapter.window());
332 let text_string = self.text();
333 let font_request = self.font_request(window_inner);
334 let scale_factor = crate::lengths::ScaleFactor::new(window_inner.scale_factor());
335 let max_width = geometry.size.width_length();
336 geometry.size = geometry.size.max(
337 window_adapter
338 .renderer()
339 .text_size(
340 font_request.clone(),
341 text_string.as_str(),
342 Some(max_width.cast()),
343 scale_factor,
344 self.wrap(),
345 )
346 .cast(),
347 );
348 geometry
349 }
350}
351
352/// Trait used to render each items.
353///
354/// The item needs to be rendered relative to its (x,y) position. For example,
355/// draw_rectangle should draw a rectangle in `(pos.x + rect.x, pos.y + rect.y)`
356#[allow(missing_docs)]
357pub trait ItemRenderer {
358 fn draw_rectangle(
359 &mut self,
360 rect: Pin<&dyn RenderRectangle>,
361 _self_rc: &ItemRc,
362 _size: LogicalSize,
363 _cache: &CachedRenderingData,
364 );
365 fn draw_border_rectangle(
366 &mut self,
367 rect: Pin<&dyn RenderBorderRectangle>,
368 _self_rc: &ItemRc,
369 _size: LogicalSize,
370 _cache: &CachedRenderingData,
371 );
372 fn draw_window_background(
373 &mut self,
374 rect: Pin<&dyn RenderRectangle>,
375 self_rc: &ItemRc,
376 size: LogicalSize,
377 cache: &CachedRenderingData,
378 );
379 fn draw_image(
380 &mut self,
381 image: Pin<&dyn RenderImage>,
382 _self_rc: &ItemRc,
383 _size: LogicalSize,
384 _cache: &CachedRenderingData,
385 );
386 fn draw_text(
387 &mut self,
388 text: Pin<&dyn RenderText>,
389 _self_rc: &ItemRc,
390 _size: LogicalSize,
391 _cache: &CachedRenderingData,
392 );
393 fn draw_text_input(
394 &mut self,
395 text_input: Pin<&TextInput>,
396 _self_rc: &ItemRc,
397 _size: LogicalSize,
398 );
399 #[cfg(feature = "std")]
400 fn draw_path(&mut self, path: Pin<&Path>, _self_rc: &ItemRc, _size: LogicalSize);
401 fn draw_box_shadow(
402 &mut self,
403 box_shadow: Pin<&BoxShadow>,
404 _self_rc: &ItemRc,
405 _size: LogicalSize,
406 );
407 fn visit_opacity(
408 &mut self,
409 opacity_item: Pin<&Opacity>,
410 _self_rc: &ItemRc,
411 _size: LogicalSize,
412 ) -> RenderingResult {
413 self.apply_opacity(opacity_item.opacity());
414 RenderingResult::ContinueRenderingChildren
415 }
416 fn visit_layer(
417 &mut self,
418 _layer_item: Pin<&Layer>,
419 _self_rc: &ItemRc,
420 _size: LogicalSize,
421 ) -> RenderingResult {
422 // Not supported
423 RenderingResult::ContinueRenderingChildren
424 }
425
426 // Apply the bounds of the Clip element, if enabled. The default implementation calls
427 // combine_clip, but the render may choose an alternate way of implementing the clip.
428 // For example the GL backend uses a layered rendering approach.
429 fn visit_clip(
430 &mut self,
431 clip_item: Pin<&Clip>,
432 item_rc: &ItemRc,
433 _size: LogicalSize,
434 ) -> RenderingResult {
435 if clip_item.clip() {
436 let geometry = item_rc.geometry();
437
438 let clip_region_valid = self.combine_clip(
439 LogicalRect::new(LogicalPoint::default(), geometry.size),
440 clip_item.logical_border_radius(),
441 clip_item.border_width(),
442 );
443
444 // If clipping is enabled but the clip element is outside the visible range, then we don't
445 // need to bother doing anything, not even rendering the children.
446 if !clip_region_valid {
447 return RenderingResult::ContinueRenderingWithoutChildren;
448 }
449 }
450 RenderingResult::ContinueRenderingChildren
451 }
452
453 /// Clip the further call until restore_state.
454 /// radius/border_width can be used for border rectangle clip.
455 /// (FIXME: consider removing radius/border_width and have another function that take a path instead)
456 /// Returns a boolean indicating the state of the new clip region: true if the clip region covers
457 /// an area; false if the clip region is empty.
458 fn combine_clip(
459 &mut self,
460 rect: LogicalRect,
461 radius: LogicalBorderRadius,
462 border_width: LogicalLength,
463 ) -> bool;
464 /// Get the current clip bounding box in the current transformed coordinate.
465 fn get_current_clip(&self) -> LogicalRect;
466
467 fn translate(&mut self, distance: LogicalVector);
468 fn translation(&self) -> LogicalVector {
469 unimplemented!()
470 }
471 fn rotate(&mut self, angle_in_degrees: f32);
472 /// Apply the opacity (between 0 and 1) for all following items until the next call to restore_state.
473 fn apply_opacity(&mut self, opacity: f32);
474
475 fn save_state(&mut self);
476 fn restore_state(&mut self);
477
478 /// Returns the scale factor
479 fn scale_factor(&self) -> f32;
480
481 /// Draw a pixmap in position indicated by the `pos`.
482 /// The pixmap will be taken from cache if the cache is valid, otherwise, update_fn will be called
483 /// with a callback that need to be called once with `fn (width, height, data)` where data are the
484 /// RGBA premultiplied pixel values
485 fn draw_cached_pixmap(
486 &mut self,
487 item_cache: &ItemRc,
488 update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
489 );
490
491 /// Draw the given string with the specified color at current (0, 0) with the default font. Mainly
492 /// used by the performance counter overlay.
493 fn draw_string(&mut self, string: &str, color: crate::Color);
494
495 fn draw_image_direct(&mut self, image: crate::graphics::Image);
496
497 /// This is called before it is being rendered (before the draw_* function).
498 /// Returns
499 /// - if the item needs to be drawn (false means it is clipped or doesn't need to be drawn)
500 /// - the geometry of the item
501 fn filter_item(
502 &mut self,
503 item: &ItemRc,
504 window_adapter: &Rc<dyn WindowAdapter>,
505 ) -> (bool, LogicalRect) {
506 let item_geometry = item.geometry();
507 // Query bounding rect untracked, as properties that affect the bounding rect are already tracked
508 // when rendering the item.
509 let bounding_rect = crate::properties::evaluate_no_tracking(|| {
510 item.bounding_rect(&item_geometry, window_adapter)
511 });
512 (self.get_current_clip().intersects(&bounding_rect), item_geometry)
513 }
514
515 fn window(&self) -> &crate::window::WindowInner;
516
517 /// Return the internal renderer
518 fn as_any(&mut self) -> Option<&mut dyn core::any::Any>;
519
520 /// Returns any rendering metrics collecting since the creation of the renderer (typically
521 /// per frame)
522 fn metrics(&self) -> crate::graphics::rendering_metrics_collector::RenderingMetrics {
523 Default::default()
524 }
525}
526
527/// Helper trait to express the features of an item renderer.
528pub trait ItemRendererFeatures {
529 /// The renderer supports applying 2D transformations to items.
530 const SUPPORTS_TRANSFORMATIONS: bool;
531}
532
533/// After rendering an item, we cache the geometry and the transform it applies to
534/// children.
535#[derive(Clone)]
536
537pub enum CachedItemBoundingBoxAndTransform {
538 /// A regular item with a translation
539 RegularItem {
540 /// The item's bounding rect relative to its parent.
541 bounding_rect: LogicalRect,
542 /// The item's offset relative to its parent.
543 offset: LogicalVector,
544 },
545 /// An item such as Rotate that defines an additional transformation
546 ItemWithTransform {
547 /// The item's bounding rect relative to its parent.
548 bounding_rect: LogicalRect,
549 /// The item's transform to apply to children.
550 transform: Box<ItemTransform>,
551 },
552 /// A clip item.
553 ClipItem {
554 /// The item's geometry relative to its parent.
555 geometry: LogicalRect,
556 },
557}
558
559impl CachedItemBoundingBoxAndTransform {
560 fn bounding_rect(&self) -> &LogicalRect {
561 match self {
562 CachedItemBoundingBoxAndTransform::RegularItem { bounding_rect, .. } => bounding_rect,
563 CachedItemBoundingBoxAndTransform::ItemWithTransform { bounding_rect, .. } => {
564 bounding_rect
565 }
566 CachedItemBoundingBoxAndTransform::ClipItem { geometry } => geometry,
567 }
568 }
569
570 fn transform(&self) -> ItemTransform {
571 match self {
572 CachedItemBoundingBoxAndTransform::RegularItem { offset, .. } => {
573 ItemTransform::translation(offset.x as f32, offset.y as f32)
574 }
575 CachedItemBoundingBoxAndTransform::ItemWithTransform { transform, .. } => **transform,
576 CachedItemBoundingBoxAndTransform::ClipItem { geometry } => {
577 ItemTransform::translation(geometry.origin.x as f32, geometry.origin.y as f32)
578 }
579 }
580 }
581
582 fn new<T: ItemRendererFeatures>(
583 item_rc: &ItemRc,
584 window_adapter: &Rc<dyn WindowAdapter>,
585 ) -> Self {
586 let geometry = item_rc.geometry();
587
588 if item_rc.borrow().as_ref().clips_children() {
589 return Self::ClipItem { geometry };
590 }
591
592 // Evaluate the bounding rect untracked, as properties that affect the bounding rect are already tracked
593 // at rendering time.
594 let bounding_rect = crate::properties::evaluate_no_tracking(|| {
595 item_rc.bounding_rect(&geometry, window_adapter)
596 });
597
598 if let Some(complex_child_transform) =
599 T::SUPPORTS_TRANSFORMATIONS.then(|| item_rc.children_transform()).flatten()
600 {
601 Self::ItemWithTransform {
602 bounding_rect,
603 transform: complex_child_transform
604 .then_translate(geometry.origin.to_vector().cast())
605 .into(),
606 }
607 } else {
608 Self::RegularItem { bounding_rect, offset: geometry.origin.to_vector() }
609 }
610 }
611}
612
613/// The cache that needs to be held by the Window for the partial rendering
614pub type PartialRenderingCache = RenderingCache<CachedItemBoundingBoxAndTransform>;
615
616/// A region composed of a few rectangles that need to be redrawn.
617#[derive(Default, Clone, Debug)]
618pub struct DirtyRegion {
619 rectangles: [euclid::Box2D<Coord, LogicalPx>; Self::MAX_COUNT],
620 count: usize,
621}
622
623impl DirtyRegion {
624 /// The maximum number of rectangles that can be stored in a DirtyRegion
625 pub(crate) const MAX_COUNT: usize = 3;
626
627 /// An iterator over the part of the region (they can overlap)
628 pub fn iter(&self) -> impl Iterator<Item = euclid::Box2D<Coord, LogicalPx>> + '_ {
629 (0..self.count).map(|x| self.rectangles[x])
630 }
631
632 /// Add a rectangle to the region.
633 ///
634 /// Note that if the region becomes too complex, it might be simplified by being bigger than the actual union.
635 pub fn add_rect(&mut self, rect: LogicalRect) {
636 self.add_box(rect.to_box2d());
637 }
638
639 /// Add a box to the region
640 ///
641 /// Note that if the region becomes too complex, it might be simplified by being bigger than the actual union.
642 pub fn add_box(&mut self, b: euclid::Box2D<Coord, LogicalPx>) {
643 if b.is_empty() {
644 return;
645 }
646 let mut i = 0;
647 while i < self.count {
648 let r = &self.rectangles[i];
649 if r.contains_box(&b) {
650 // the rectangle is already in the union
651 return;
652 } else if b.contains_box(r) {
653 self.rectangles.swap(i, self.count - 1);
654 self.count -= 1;
655 continue;
656 }
657 i += 1;
658 }
659
660 if self.count < Self::MAX_COUNT {
661 self.rectangles[self.count] = b;
662 self.count += 1;
663 } else {
664 let best_merge = (0..self.count)
665 .map(|i| (i, self.rectangles[i].union(&b).area() - self.rectangles[i].area()))
666 .min_by(|a, b| PartialOrd::partial_cmp(&a.1, &b.1).unwrap())
667 .expect("There should always be rectangles")
668 .0;
669 self.rectangles[best_merge] = self.rectangles[best_merge].union(&b);
670 }
671 }
672
673 /// Make an union of two regions.
674 ///
675 /// Note that if the region becomes too complex, it might be simplified by being bigger than the actual union
676 #[must_use]
677 pub fn union(&self, other: &Self) -> Self {
678 let mut s = self.clone();
679 for o in other.iter() {
680 s.add_box(o)
681 }
682 s
683 }
684
685 /// Bounding rectangle of the region.
686 #[must_use]
687 pub fn bounding_rect(&self) -> LogicalRect {
688 if self.count == 0 {
689 return Default::default();
690 }
691 let mut r = self.rectangles[0];
692 for i in 1..self.count {
693 r = r.union(&self.rectangles[i]);
694 }
695 r.to_rect()
696 }
697
698 /// Intersection of a region and a rectangle.
699 #[must_use]
700 pub fn intersection(&self, other: LogicalRect) -> DirtyRegion {
701 let mut ret = self.clone();
702 let other = other.to_box2d();
703 let mut i = 0;
704 while i < ret.count {
705 if let Some(x) = ret.rectangles[i].intersection(&other) {
706 ret.rectangles[i] = x;
707 } else {
708 ret.count -= 1;
709 ret.rectangles.swap(i, ret.count);
710 continue;
711 }
712 i += 1;
713 }
714 ret
715 }
716
717 fn draw_intersects(&self, clipped_geom: LogicalRect) -> bool {
718 let b = clipped_geom.to_box2d();
719 self.iter().any(|r| r.intersects(&b))
720 }
721}
722
723impl From<LogicalRect> for DirtyRegion {
724 fn from(value: LogicalRect) -> Self {
725 let mut s: DirtyRegion = Self::default();
726 s.add_rect(value);
727 s
728 }
729}
730
731/// This enum describes which parts of the buffer passed to the [`SoftwareRenderer`](crate::software_renderer::SoftwareRenderer) may be re-used to speed up painting.
732// FIXME: #[non_exhaustive] #3023
733#[derive(PartialEq, Eq, Debug, Clone, Default, Copy)]
734pub enum RepaintBufferType {
735 #[default]
736 /// The full window is always redrawn. No attempt at partial rendering will be made.
737 NewBuffer,
738 /// Only redraw the parts that have changed since the previous call to render().
739 ///
740 /// This variant assumes that the same buffer is passed on every call to render() and
741 /// that it still contains the previously rendered frame.
742 ReusedBuffer,
743
744 /// Redraw the part that have changed since the last two frames were drawn.
745 ///
746 /// This is used when using double buffering and swapping of the buffers.
747 SwappedBuffers,
748}
749
750/// Put this structure in the renderer to help with partial rendering
751pub struct PartialRenderer<'a, T> {
752 cache: &'a RefCell<PartialRenderingCache>,
753 /// The region of the screen which is considered dirty and that should be repainted
754 pub dirty_region: DirtyRegion,
755 /// The actual renderer which the drawing call will be forwarded to
756 pub actual_renderer: T,
757 /// The window adapter the renderer is rendering into.
758 pub window_adapter: Rc<dyn WindowAdapter>,
759}
760
761impl<'a, T: ItemRenderer + ItemRendererFeatures> PartialRenderer<'a, T> {
762 /// Create a new PartialRenderer
763 pub fn new(
764 cache: &'a RefCell<PartialRenderingCache>,
765 initial_dirty_region: DirtyRegion,
766 actual_renderer: T,
767 ) -> Self {
768 let window_adapter = actual_renderer.window().window_adapter();
769 Self { cache, dirty_region: initial_dirty_region, actual_renderer, window_adapter }
770 }
771
772 /// Visit the tree of item and compute what are the dirty regions
773 pub fn compute_dirty_regions(
774 &mut self,
775 component: &ItemTreeRc,
776 origin: LogicalPoint,
777 size: LogicalSize,
778 ) {
779 #[derive(Clone, Copy)]
780 struct ComputeDirtyRegionState {
781 transform_to_screen: ItemTransform,
782 old_transform_to_screen: ItemTransform,
783 clipped: LogicalRect,
784 must_refresh_children: bool,
785 }
786
787 impl ComputeDirtyRegionState {
788 /// Adjust transform_to_screen and old_transform_to_screen to map from item coordinates
789 /// to the screen when using it on a child, specified by its children transform.
790 fn adjust_transforms_for_child(
791 &mut self,
792 children_transform: &ItemTransform,
793 old_children_transform: &ItemTransform,
794 ) {
795 self.transform_to_screen = children_transform.then(&self.transform_to_screen);
796 self.old_transform_to_screen =
797 old_children_transform.then(&self.old_transform_to_screen);
798 }
799 }
800
801 crate::item_tree::visit_items(
802 component,
803 crate::item_tree::TraversalOrder::BackToFront,
804 |component, item, index, state| {
805 let mut new_state = *state;
806 let mut borrowed = self.cache.borrow_mut();
807 let item_rc = ItemRc::new(component.clone(), index);
808
809 match item.cached_rendering_data_offset().get_entry(&mut borrowed) {
810 Some(CachedGraphicsData {
811 data: cached_geom,
812 dependency_tracker: Some(tr),
813 }) => {
814 if tr.is_dirty() {
815 let old_geom = cached_geom.clone();
816 drop(borrowed);
817 let new_geom = crate::properties::evaluate_no_tracking(|| {
818 CachedItemBoundingBoxAndTransform::new::<T>(
819 &item_rc,
820 &self.window_adapter,
821 )
822 });
823
824 self.mark_dirty_rect(
825 old_geom.bounding_rect(),
826 state.old_transform_to_screen,
827 &state.clipped,
828 );
829 self.mark_dirty_rect(
830 new_geom.bounding_rect(),
831 state.transform_to_screen,
832 &state.clipped,
833 );
834
835 new_state.adjust_transforms_for_child(
836 &new_geom.transform(),
837 &old_geom.transform(),
838 );
839
840 if ItemRef::downcast_pin::<Clip>(item).is_some()
841 || ItemRef::downcast_pin::<Opacity>(item).is_some()
842 {
843 // When the opacity or the clip change, this will impact all the children, including
844 // the ones outside the element, regardless if they are themselves dirty or not.
845 new_state.must_refresh_children = true;
846 }
847
848 ItemVisitorResult::Continue(new_state)
849 } else {
850 tr.as_ref().register_as_dependency_to_current_binding();
851
852 if state.must_refresh_children
853 || new_state.transform_to_screen
854 != new_state.old_transform_to_screen
855 {
856 self.mark_dirty_rect(
857 cached_geom.bounding_rect(),
858 state.old_transform_to_screen,
859 &state.clipped,
860 );
861 self.mark_dirty_rect(
862 cached_geom.bounding_rect(),
863 state.transform_to_screen,
864 &state.clipped,
865 );
866 }
867
868 new_state.adjust_transforms_for_child(
869 &cached_geom.transform(),
870 &cached_geom.transform(),
871 );
872
873 if let CachedItemBoundingBoxAndTransform::ClipItem { geometry } =
874 &cached_geom
875 {
876 new_state.clipped = new_state
877 .clipped
878 .intersection(
879 &state
880 .transform_to_screen
881 .outer_transformed_rect(&geometry.cast())
882 .cast()
883 .union(
884 &state
885 .old_transform_to_screen
886 .outer_transformed_rect(&geometry.cast())
887 .cast(),
888 ),
889 )
890 .unwrap_or_default();
891 }
892 ItemVisitorResult::Continue(new_state)
893 }
894 }
895 _ => {
896 drop(borrowed);
897 let bounding_rect = crate::properties::evaluate_no_tracking(|| {
898 let geom = CachedItemBoundingBoxAndTransform::new::<T>(
899 &item_rc,
900 &self.window_adapter,
901 );
902
903 new_state
904 .adjust_transforms_for_child(&geom.transform(), &geom.transform());
905
906 if let CachedItemBoundingBoxAndTransform::ClipItem { geometry } = geom {
907 new_state.clipped = new_state
908 .clipped
909 .intersection(
910 &state
911 .transform_to_screen
912 .outer_transformed_rect(&geometry.cast())
913 .cast(),
914 )
915 .unwrap_or_default();
916 }
917 *geom.bounding_rect()
918 });
919 self.mark_dirty_rect(
920 &bounding_rect,
921 state.transform_to_screen,
922 &state.clipped,
923 );
924 ItemVisitorResult::Continue(new_state)
925 }
926 }
927 },
928 {
929 let initial_transform =
930 euclid::Transform2D::translation(origin.x as f32, origin.y as f32);
931 ComputeDirtyRegionState {
932 transform_to_screen: initial_transform,
933 old_transform_to_screen: initial_transform,
934 clipped: LogicalRect::from_size(size),
935 must_refresh_children: false,
936 }
937 },
938 );
939 }
940
941 fn mark_dirty_rect(
942 &mut self,
943 rect: &LogicalRect,
944 transform: ItemTransform,
945 clip_rect: &LogicalRect,
946 ) {
947 if !rect.is_empty() {
948 if let Some(rect) =
949 transform.outer_transformed_rect(&rect.cast()).cast().intersection(clip_rect)
950 {
951 self.dirty_region.add_rect(rect);
952 }
953 }
954 }
955
956 fn do_rendering(
957 cache: &RefCell<PartialRenderingCache>,
958 rendering_data: &CachedRenderingData,
959 render_fn: impl FnOnce() -> CachedItemBoundingBoxAndTransform,
960 ) {
961 let mut cache = cache.borrow_mut();
962 if let Some(entry) = rendering_data.get_entry(&mut cache) {
963 entry
964 .dependency_tracker
965 .get_or_insert_with(|| Box::pin(PropertyTracker::default()))
966 .as_ref()
967 .evaluate(render_fn);
968 } else {
969 let cache_entry = crate::graphics::CachedGraphicsData::new(render_fn);
970 rendering_data.cache_index.set(cache.insert(cache_entry));
971 rendering_data.cache_generation.set(cache.generation());
972 }
973 }
974
975 /// Move the actual renderer
976 pub fn into_inner(self) -> T {
977 self.actual_renderer
978 }
979}
980
981macro_rules! forward_rendering_call {
982 (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
983 fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize) $(-> $Ret)? {
984 let mut ret = None;
985 Self::do_rendering(&self.cache, &obj.cached_rendering_data, || {
986 ret = Some(self.actual_renderer.$fn(obj, item_rc, size));
987 CachedItemBoundingBoxAndTransform::new::<T>(&item_rc, &self.window_adapter)
988 });
989 ret.unwrap_or_default()
990 }
991 };
992}
993
994macro_rules! forward_rendering_call2 {
995 (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
996 fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize, cache: &CachedRenderingData) $(-> $Ret)? {
997 let mut ret = None;
998 Self::do_rendering(&self.cache, &cache, || {
999 ret = Some(self.actual_renderer.$fn(obj, item_rc, size, &cache));
1000 CachedItemBoundingBoxAndTransform::new::<T>(&item_rc, &self.window_adapter)
1001 });
1002 ret.unwrap_or_default()
1003 }
1004 };
1005}
1006
1007impl<T: ItemRenderer + ItemRendererFeatures> ItemRenderer for PartialRenderer<'_, T> {
1008 fn filter_item(
1009 &mut self,
1010 item_rc: &ItemRc,
1011 window_adapter: &Rc<dyn WindowAdapter>,
1012 ) -> (bool, LogicalRect) {
1013 let item = item_rc.borrow();
1014 let eval = || {
1015 // registers dependencies on the geometry and clip properties.
1016 CachedItemBoundingBoxAndTransform::new::<T>(item_rc, window_adapter)
1017 };
1018
1019 let rendering_data = item.cached_rendering_data_offset();
1020 let mut cache = self.cache.borrow_mut();
1021 let item_bounding_rect = match rendering_data.get_entry(&mut cache) {
1022 Some(CachedGraphicsData { data, dependency_tracker }) => {
1023 dependency_tracker
1024 .get_or_insert_with(|| Box::pin(PropertyTracker::default()))
1025 .as_ref()
1026 .evaluate_if_dirty(|| *data = eval());
1027 *data.bounding_rect()
1028 }
1029 None => {
1030 let cache_entry = crate::graphics::CachedGraphicsData::new(eval);
1031 let geom = cache_entry.data.clone();
1032 rendering_data.cache_index.set(cache.insert(cache_entry));
1033 rendering_data.cache_generation.set(cache.generation());
1034 *geom.bounding_rect()
1035 }
1036 };
1037
1038 let clipped_geom = self.get_current_clip().intersection(&item_bounding_rect);
1039 let draw = clipped_geom.is_some_and(|clipped_geom| {
1040 let clipped_geom = clipped_geom.translate(self.translation());
1041 self.dirty_region.draw_intersects(clipped_geom)
1042 });
1043
1044 // Query untracked, as the bounding rect calculation already registers a dependency on the geometry.
1045 let item_geometry = crate::properties::evaluate_no_tracking(|| item_rc.geometry());
1046
1047 (draw, item_geometry)
1048 }
1049
1050 forward_rendering_call2!(fn draw_rectangle(dyn RenderRectangle));
1051 forward_rendering_call2!(fn draw_border_rectangle(dyn RenderBorderRectangle));
1052 forward_rendering_call2!(fn draw_window_background(dyn RenderRectangle));
1053 forward_rendering_call2!(fn draw_image(dyn RenderImage));
1054 forward_rendering_call2!(fn draw_text(dyn RenderText));
1055 forward_rendering_call!(fn draw_text_input(TextInput));
1056 #[cfg(feature = "std")]
1057 forward_rendering_call!(fn draw_path(Path));
1058 forward_rendering_call!(fn draw_box_shadow(BoxShadow));
1059
1060 forward_rendering_call!(fn visit_clip(Clip) -> RenderingResult);
1061 forward_rendering_call!(fn visit_opacity(Opacity) -> RenderingResult);
1062
1063 fn combine_clip(
1064 &mut self,
1065 rect: LogicalRect,
1066 radius: LogicalBorderRadius,
1067 border_width: LogicalLength,
1068 ) -> bool {
1069 self.actual_renderer.combine_clip(rect, radius, border_width)
1070 }
1071
1072 fn get_current_clip(&self) -> LogicalRect {
1073 self.actual_renderer.get_current_clip()
1074 }
1075
1076 fn translate(&mut self, distance: LogicalVector) {
1077 self.actual_renderer.translate(distance)
1078 }
1079 fn translation(&self) -> LogicalVector {
1080 self.actual_renderer.translation()
1081 }
1082
1083 fn rotate(&mut self, angle_in_degrees: f32) {
1084 self.actual_renderer.rotate(angle_in_degrees)
1085 }
1086
1087 fn apply_opacity(&mut self, opacity: f32) {
1088 self.actual_renderer.apply_opacity(opacity)
1089 }
1090
1091 fn save_state(&mut self) {
1092 self.actual_renderer.save_state()
1093 }
1094
1095 fn restore_state(&mut self) {
1096 self.actual_renderer.restore_state()
1097 }
1098
1099 fn scale_factor(&self) -> f32 {
1100 self.actual_renderer.scale_factor()
1101 }
1102
1103 fn draw_cached_pixmap(
1104 &mut self,
1105 item_rc: &ItemRc,
1106 update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
1107 ) {
1108 self.actual_renderer.draw_cached_pixmap(item_rc, update_fn)
1109 }
1110
1111 fn draw_string(&mut self, string: &str, color: crate::Color) {
1112 self.actual_renderer.draw_string(string, color)
1113 }
1114
1115 fn draw_image_direct(&mut self, image: crate::graphics::image::Image) {
1116 self.actual_renderer.draw_image_direct(image)
1117 }
1118
1119 fn window(&self) -> &crate::window::WindowInner {
1120 self.actual_renderer.window()
1121 }
1122
1123 fn as_any(&mut self) -> Option<&mut dyn core::any::Any> {
1124 self.actual_renderer.as_any()
1125 }
1126}
1127
1128/// This struct holds the state of the partial renderer between different frames, in particular the cache of the bounding rect
1129/// of each item. This permits a more fine-grained computation of the region that needs to be repainted.
1130#[derive(Default)]
1131pub struct PartialRenderingState {
1132 partial_cache: RefCell<PartialRenderingCache>,
1133 /// This is the area which we are going to redraw in the next frame, no matter if the items are dirty or not
1134 force_dirty: RefCell<DirtyRegion>,
1135 /// Force a redraw in the next frame, no matter what's dirty. Use only as a last resort.
1136 force_screen_refresh: Cell<bool>,
1137}
1138
1139impl PartialRenderingState {
1140 /// Creates a partial renderer that's initialized with the partial rendering caches maintained in this state structure.
1141 /// Call [`Self::apply_dirty_region`] after this function to compute the correct partial rendering region.
1142 pub fn create_partial_renderer<T: ItemRenderer + ItemRendererFeatures>(
1143 &self,
1144 renderer: T,
1145 ) -> PartialRenderer<'_, T> {
1146 PartialRenderer::new(&self.partial_cache, self.force_dirty.take(), renderer)
1147 }
1148
1149 /// Compute the correct partial rendering region based on the components to be drawn, the bounding rectangles of
1150 /// changes items within, and the current repaint buffer type. Returns the computed dirty region just for this frame.
1151 /// The provided buffer_dirty_region specifies which area of the buffer is known to *additionally* require repainting,
1152 /// where `None` means that buffer is not known to be dirty beyond what applies to this frame (reused buffer).
1153 pub fn apply_dirty_region<T: ItemRenderer + ItemRendererFeatures>(
1154 &self,
1155 partial_renderer: &mut PartialRenderer<'_, T>,
1156 components: &[(&ItemTreeRc, LogicalPoint)],
1157 logical_window_size: LogicalSize,
1158 dirty_region_of_existing_buffer: Option<DirtyRegion>,
1159 ) -> DirtyRegion {
1160 for (component, origin) in components {
1161 partial_renderer.compute_dirty_regions(component, *origin, logical_window_size);
1162 }
1163
1164 let screen_region = LogicalRect::from_size(logical_window_size);
1165
1166 if self.force_screen_refresh.take() {
1167 partial_renderer.dirty_region = screen_region.into();
1168 }
1169
1170 let region_to_repaint = partial_renderer.dirty_region.clone();
1171
1172 partial_renderer.dirty_region = match dirty_region_of_existing_buffer {
1173 Some(dirty_region) => partial_renderer.dirty_region.union(&dirty_region),
1174 None => partial_renderer.dirty_region.clone(),
1175 }
1176 .intersection(screen_region);
1177
1178 region_to_repaint
1179 }
1180
1181 /// Add the specified region to the list of regions to include in the next rendering.
1182 pub fn mark_dirty_region(&self, region: DirtyRegion) {
1183 self.force_dirty.replace_with(|r| r.union(&region));
1184 }
1185
1186 /// Call this from your renderer's `free_graphics_resources` function to ensure that the cached item geometries
1187 /// are cleared for the destroyed items in the item tree.
1188 pub fn free_graphics_resources(&self, items: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>) {
1189 for item in items {
1190 item.cached_rendering_data_offset().release(&mut self.partial_cache.borrow_mut());
1191 }
1192
1193 // We don't have a way to determine the screen region of the delete items, what's in the cache is relative. So
1194 // as a last resort, refresh everything.
1195 self.force_screen_refresh.set(true)
1196 }
1197
1198 /// Clears the partial rendering cache. Use this for example when the entire undering window surface changes.
1199 pub fn clear_cache(&self) {
1200 self.partial_cache.borrow_mut().clear();
1201 }
1202
1203 /// Force re-rendering of the entire window region the next time a partial renderer is created.
1204 pub fn force_screen_refresh(&self) {
1205 self.force_screen_refresh.set(true);
1206 }
1207}
1208
1209#[test]
1210fn dirty_region_no_intersection() {
1211 let mut region = DirtyRegion::default();
1212 region.add_rect(LogicalRect::new(LogicalPoint::new(10., 10.), LogicalSize::new(16., 16.)));
1213 region.add_rect(LogicalRect::new(LogicalPoint::new(100., 100.), LogicalSize::new(16., 16.)));
1214 region.add_rect(LogicalRect::new(LogicalPoint::new(200., 100.), LogicalSize::new(16., 16.)));
1215 let i = region
1216 .intersection(LogicalRect::new(LogicalPoint::new(50., 50.), LogicalSize::new(10., 10.)));
1217 assert_eq!(i.iter().count(), 0);
1218}
1219