1use crate::Coord;
19use crate::item_rendering::{
20 ItemRenderer, ItemRendererFeatures, RenderBorderRectangle, RenderImage, RenderRectangle,
21 RenderText,
22};
23use crate::item_tree::{ItemTreeRc, ItemTreeWeak, ItemVisitorResult};
24#[cfg(feature = "std")]
25use crate::items::Path;
26use crate::items::{BoxShadow, Clip, ItemRc, ItemRef, Opacity, RenderingResult, TextInput};
27use crate::lengths::{
28 ItemTransform, LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalPx, LogicalRect,
29 LogicalSize, LogicalVector,
30};
31use crate::properties::PropertyTracker;
32use crate::window::WindowAdapter;
33use alloc::boxed::Box;
34use alloc::rc::Rc;
35use core::cell::{Cell, RefCell};
36use core::pin::Pin;
37
38#[derive(Default, Debug)]
41#[repr(C)]
42pub struct CachedRenderingData {
43 pub(crate) cache_index: Cell<usize>,
45 pub(crate) cache_generation: Cell<usize>,
49}
50
51impl CachedRenderingData {
52 fn release(
56 &self,
57 cache: &mut PartialRendererCache,
58 ) -> Option<CachedItemBoundingBoxAndTransform> {
59 if self.cache_generation.get() == cache.generation() {
60 let index = self.cache_index.get();
61 self.cache_generation.set(0);
62 Some(cache.remove(index).data)
63 } else {
64 None
65 }
66 }
67
68 fn get_entry<'a>(
70 &self,
71 cache: &'a mut PartialRendererCache,
72 ) -> Option<&'a mut PartialRenderingCachedData> {
73 let index = self.cache_index.get();
74 if self.cache_generation.get() == cache.generation() { cache.get_mut(index) } else { None }
75 }
76}
77
78#[derive(Clone, PartialEq)]
81pub enum CachedItemBoundingBoxAndTransform {
82 RegularItem {
84 bounding_rect: LogicalRect,
86 offset: LogicalVector,
88 },
89 ItemWithTransform {
91 bounding_rect: LogicalRect,
93 transform: Box<ItemTransform>,
95 },
96 ClipItem {
98 geometry: LogicalRect,
100 },
101}
102
103impl CachedItemBoundingBoxAndTransform {
104 fn bounding_rect(&self) -> &LogicalRect {
105 match self {
106 CachedItemBoundingBoxAndTransform::RegularItem { bounding_rect, .. } => bounding_rect,
107 CachedItemBoundingBoxAndTransform::ItemWithTransform { bounding_rect, .. } => {
108 bounding_rect
109 }
110 CachedItemBoundingBoxAndTransform::ClipItem { geometry } => geometry,
111 }
112 }
113
114 fn transform(&self) -> ItemTransform {
115 match self {
116 CachedItemBoundingBoxAndTransform::RegularItem { offset, .. } => {
117 ItemTransform::translation(offset.x as f32, offset.y as f32)
118 }
119 CachedItemBoundingBoxAndTransform::ItemWithTransform { transform, .. } => **transform,
120 CachedItemBoundingBoxAndTransform::ClipItem { geometry } => {
121 ItemTransform::translation(geometry.origin.x as f32, geometry.origin.y as f32)
122 }
123 }
124 }
125
126 fn new<T: ItemRendererFeatures>(
127 item_rc: &ItemRc,
128 window_adapter: &Rc<dyn WindowAdapter>,
129 ) -> Self {
130 let geometry = item_rc.geometry();
131
132 if item_rc.borrow().as_ref().clips_children() {
133 return Self::ClipItem { geometry };
134 }
135
136 let bounding_rect = crate::properties::evaluate_no_tracking(|| {
139 item_rc.bounding_rect(&geometry, window_adapter)
140 });
141
142 if let Some(complex_child_transform) = (T::SUPPORTS_TRANSFORMATIONS
143 && window_adapter.renderer().supports_transformations())
144 .then(|| item_rc.children_transform())
145 .flatten()
146 {
147 Self::ItemWithTransform {
148 bounding_rect,
149 transform: complex_child_transform
150 .then_translate(geometry.origin.to_vector().cast())
151 .into(),
152 }
153 } else {
154 Self::RegularItem { bounding_rect, offset: geometry.origin.to_vector() }
155 }
156 }
157}
158
159struct PartialRenderingCachedData {
160 pub data: CachedItemBoundingBoxAndTransform,
162 pub tracker: Option<core::pin::Pin<Box<PropertyTracker>>>,
164}
165impl PartialRenderingCachedData {
166 fn new(data: CachedItemBoundingBoxAndTransform) -> Self {
167 Self { data, tracker: None }
168 }
169}
170
171struct PartialRendererCache {
173 slab: slab::Slab<PartialRenderingCachedData>,
174 generation: usize,
175}
176
177impl Default for PartialRendererCache {
178 fn default() -> Self {
179 Self { slab: Default::default(), generation: 1 }
180 }
181}
182
183impl PartialRendererCache {
184 pub fn generation(&self) -> usize {
187 self.generation
188 }
189
190 pub fn get_mut(&mut self, index: usize) -> Option<&mut PartialRenderingCachedData> {
192 self.slab.get_mut(index)
193 }
194
195 pub fn insert(&mut self, data: PartialRenderingCachedData) -> usize {
197 self.slab.insert(data)
198 }
199
200 pub fn remove(&mut self, index: usize) -> PartialRenderingCachedData {
202 self.slab.remove(index)
203 }
204
205 pub fn clear(&mut self) {
208 self.slab.clear();
209 self.generation += 1;
210 }
211}
212
213#[derive(Default, Clone)]
215pub struct DirtyRegion {
216 rectangles: [euclid::Box2D<Coord, LogicalPx>; Self::MAX_COUNT],
217 count: usize,
218}
219
220impl core::fmt::Debug for DirtyRegion {
221 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
222 write!(f, "{:?}", &self.rectangles[..self.count])
223 }
224}
225
226impl DirtyRegion {
227 pub const MAX_COUNT: usize = 3;
229
230 pub fn iter(&self) -> impl Iterator<Item = euclid::Box2D<Coord, LogicalPx>> + '_ {
232 (0..self.count).map(|x| self.rectangles[x])
233 }
234
235 pub fn add_rect(&mut self, rect: LogicalRect) {
239 self.add_box(rect.to_box2d());
240 }
241
242 pub fn add_box(&mut self, b: euclid::Box2D<Coord, LogicalPx>) {
246 if b.is_empty() {
247 return;
248 }
249 let mut i = 0;
250 while i < self.count {
251 let r = &self.rectangles[i];
252 if r.contains_box(&b) {
253 return;
255 } else if b.contains_box(r) {
256 self.rectangles.swap(i, self.count - 1);
257 self.count -= 1;
258 continue;
259 }
260 i += 1;
261 }
262
263 if self.count < Self::MAX_COUNT {
264 self.rectangles[self.count] = b;
265 self.count += 1;
266 } else {
267 let best_merge = (0..self.count)
268 .map(|i| (i, self.rectangles[i].union(&b).area() - self.rectangles[i].area()))
269 .min_by(|a, b| PartialOrd::partial_cmp(&a.1, &b.1).unwrap())
270 .expect("There should always be rectangles")
271 .0;
272 self.rectangles[best_merge] = self.rectangles[best_merge].union(&b);
273 }
274 }
275
276 #[must_use]
280 pub fn union(&self, other: &Self) -> Self {
281 let mut s = self.clone();
282 for o in other.iter() {
283 s.add_box(o)
284 }
285 s
286 }
287
288 #[must_use]
290 pub fn bounding_rect(&self) -> LogicalRect {
291 if self.count == 0 {
292 return Default::default();
293 }
294 let mut r = self.rectangles[0];
295 for i in 1..self.count {
296 r = r.union(&self.rectangles[i]);
297 }
298 r.to_rect()
299 }
300
301 #[must_use]
303 pub fn intersection(&self, other: LogicalRect) -> DirtyRegion {
304 let mut ret = self.clone();
305 let other = other.to_box2d();
306 let mut i = 0;
307 while i < ret.count {
308 if let Some(x) = ret.rectangles[i].intersection(&other) {
309 ret.rectangles[i] = x;
310 } else {
311 ret.count -= 1;
312 ret.rectangles.swap(i, ret.count);
313 continue;
314 }
315 i += 1;
316 }
317 ret
318 }
319
320 fn draw_intersects(&self, clipped_geom: LogicalRect) -> bool {
321 let b = clipped_geom.to_box2d();
322 self.iter().any(|r| r.intersects(&b))
323 }
324}
325
326impl From<LogicalRect> for DirtyRegion {
327 fn from(value: LogicalRect) -> Self {
328 let mut s = Self::default();
329 s.add_rect(value);
330 s
331 }
332}
333
334#[derive(PartialEq, Eq, Debug, Clone, Default, Copy)]
337pub enum RepaintBufferType {
338 #[default]
339 NewBuffer,
341 ReusedBuffer,
346
347 SwappedBuffers,
351}
352
353pub struct PartialRenderer<'a, T> {
357 cache: &'a RefCell<PartialRendererCache>,
358 pub dirty_region: DirtyRegion,
360 pub actual_renderer: T,
362 pub window_adapter: Rc<dyn WindowAdapter>,
364}
365
366impl<'a, T: ItemRenderer + ItemRendererFeatures> PartialRenderer<'a, T> {
367 fn new(
369 cache: &'a RefCell<PartialRendererCache>,
370 initial_dirty_region: DirtyRegion,
371 actual_renderer: T,
372 ) -> Self {
373 let window_adapter = actual_renderer.window().window_adapter();
374 Self { cache, dirty_region: initial_dirty_region, actual_renderer, window_adapter }
375 }
376
377 pub fn compute_dirty_regions(
379 &mut self,
380 component: &ItemTreeRc,
381 origin: LogicalPoint,
382 size: LogicalSize,
383 ) {
384 #[derive(Clone, Copy)]
385 struct ComputeDirtyRegionState {
386 transform_to_screen: ItemTransform,
387 old_transform_to_screen: ItemTransform,
388 clipped: LogicalRect,
389 must_refresh_children: bool,
390 }
391
392 impl ComputeDirtyRegionState {
393 fn adjust_transforms_for_child(
396 &mut self,
397 children_transform: &ItemTransform,
398 old_children_transform: &ItemTransform,
399 ) {
400 self.transform_to_screen = children_transform.then(&self.transform_to_screen);
401 self.old_transform_to_screen =
402 old_children_transform.then(&self.old_transform_to_screen);
403 }
404 }
405
406 crate::item_tree::visit_items(
407 component,
408 crate::item_tree::TraversalOrder::BackToFront,
409 |component, item, index, state| {
410 let mut new_state = *state;
411 let item_rc = ItemRc::new(component.clone(), index);
412 let new_geom =
413 CachedItemBoundingBoxAndTransform::new::<T>(&item_rc, &self.window_adapter);
414
415 let rendering_data = item.cached_rendering_data_offset();
416 let mut cache = self.cache.borrow_mut();
417 match rendering_data.get_entry(&mut cache) {
418 Some(PartialRenderingCachedData { data: cached_geom, tracker }) => {
419 let rendering_dirty = tracker.as_ref().is_some_and(|tr| tr.is_dirty());
420 let old_geom = cached_geom.clone();
421
422 let geometry_changed = old_geom != new_geom;
423 if ItemRef::downcast_pin::<Clip>(item).is_some()
424 || ItemRef::downcast_pin::<Opacity>(item).is_some()
425 {
426 new_state.must_refresh_children |= rendering_dirty || geometry_changed;
429
430 if rendering_dirty {
431 *tracker = None;
433 }
434 }
435
436 if geometry_changed {
437 self.mark_dirty_rect(
438 old_geom.bounding_rect(),
439 state.old_transform_to_screen,
440 &state.clipped,
441 );
442 self.mark_dirty_rect(
443 new_geom.bounding_rect(),
444 state.transform_to_screen,
445 &state.clipped,
446 );
447
448 new_state.adjust_transforms_for_child(
449 &new_geom.transform(),
450 &old_geom.transform(),
451 );
452
453 *cached_geom = new_geom;
454
455 return ItemVisitorResult::Continue(new_state);
456 }
457
458 new_state.adjust_transforms_for_child(
459 &cached_geom.transform(),
460 &cached_geom.transform(),
461 );
462
463 if rendering_dirty {
464 self.mark_dirty_rect(
465 cached_geom.bounding_rect(),
466 state.transform_to_screen,
467 &state.clipped,
468 );
469
470 ItemVisitorResult::Continue(new_state)
471 } else {
472 if state.must_refresh_children
473 || new_state.transform_to_screen
474 != new_state.old_transform_to_screen
475 {
476 self.mark_dirty_rect(
477 cached_geom.bounding_rect(),
478 state.old_transform_to_screen,
479 &state.clipped,
480 );
481 self.mark_dirty_rect(
482 cached_geom.bounding_rect(),
483 state.transform_to_screen,
484 &state.clipped,
485 );
486 } else if let Some(tr) = &tracker {
487 tr.as_ref().register_as_dependency_to_current_binding();
488 }
489
490 if let CachedItemBoundingBoxAndTransform::ClipItem { geometry } =
491 &cached_geom
492 {
493 new_state.clipped = new_state
494 .clipped
495 .intersection(
496 &state
497 .transform_to_screen
498 .outer_transformed_rect(&geometry.cast())
499 .cast()
500 .union(
501 &state
502 .old_transform_to_screen
503 .outer_transformed_rect(&geometry.cast())
504 .cast(),
505 ),
506 )
507 .unwrap_or_default();
508 if new_state.clipped.is_empty() {
509 return ItemVisitorResult::SkipChildren;
510 }
511 }
512 ItemVisitorResult::Continue(new_state)
513 }
514 }
515 None => {
516 let cache_entry = PartialRenderingCachedData::new(new_geom.clone());
517 rendering_data.cache_index.set(cache.insert(cache_entry));
518 rendering_data.cache_generation.set(cache.generation());
519
520 new_state.adjust_transforms_for_child(
521 &new_geom.transform(),
522 &new_geom.transform(),
523 );
524
525 if let CachedItemBoundingBoxAndTransform::ClipItem { geometry } = new_geom {
526 new_state.clipped = new_state
527 .clipped
528 .intersection(
529 &state
530 .transform_to_screen
531 .outer_transformed_rect(&geometry.cast())
532 .cast(),
533 )
534 .unwrap_or_default();
535 }
536
537 self.mark_dirty_rect(
538 new_geom.bounding_rect(),
539 state.transform_to_screen,
540 &state.clipped,
541 );
542 if new_state.clipped.is_empty() {
543 ItemVisitorResult::SkipChildren
544 } else {
545 ItemVisitorResult::Continue(new_state)
546 }
547 }
548 }
549 },
550 {
551 let initial_transform =
552 euclid::Transform2D::translation(origin.x as f32, origin.y as f32);
553 ComputeDirtyRegionState {
554 transform_to_screen: initial_transform,
555 old_transform_to_screen: initial_transform,
556 clipped: LogicalRect::from_size(size),
557 must_refresh_children: false,
558 }
559 },
560 );
561 }
562
563 fn mark_dirty_rect(
564 &mut self,
565 rect: &LogicalRect,
566 transform: ItemTransform,
567 clip_rect: &LogicalRect,
568 ) {
569 #[cfg(not(slint_int_coord))]
570 if !rect.origin.is_finite() {
571 return;
573 }
574
575 if !rect.is_empty() {
576 if let Some(rect) =
577 transform.outer_transformed_rect(&rect.cast()).cast().intersection(clip_rect)
578 {
579 self.dirty_region.add_rect(rect);
580 }
581 }
582 }
583
584 fn do_rendering(
585 cache: &RefCell<PartialRendererCache>,
586 rendering_data: &CachedRenderingData,
587 item_rc: &ItemRc,
588 render_fn: impl FnOnce(),
589 ) {
590 let mut cache = cache.borrow_mut();
591 if let Some(entry) = rendering_data.get_entry(&mut cache) {
592 entry
593 .tracker
594 .get_or_insert_with(|| Box::pin(PropertyTracker::default()))
595 .as_ref()
596 .evaluate(render_fn);
597 } else {
598 item_rc.geometry();
601 render_fn();
602 }
603 }
604
605 pub fn into_inner(self) -> T {
607 self.actual_renderer
608 }
609}
610
611macro_rules! forward_rendering_call {
612 (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
613 fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize) $(-> $Ret)? {
614 let mut ret = None;
615 Self::do_rendering(&self.cache, &obj.cached_rendering_data, item_rc, || {
616 ret = Some(self.actual_renderer.$fn(obj, item_rc, size));
617 });
618 ret.unwrap_or_default()
619 }
620 };
621}
622
623macro_rules! forward_rendering_call2 {
624 (fn $fn:ident($Ty:ty) $(-> $Ret:ty)?) => {
625 fn $fn(&mut self, obj: Pin<&$Ty>, item_rc: &ItemRc, size: LogicalSize, cache: &CachedRenderingData) $(-> $Ret)? {
626 let mut ret = None;
627 Self::do_rendering(&self.cache, &cache, item_rc, || {
628 ret = Some(self.actual_renderer.$fn(obj, item_rc, size, &cache));
629 });
630 ret.unwrap_or_default()
631 }
632 };
633}
634
635impl<T: ItemRenderer + ItemRendererFeatures> ItemRenderer for PartialRenderer<'_, T> {
636 fn filter_item(
637 &mut self,
638 item_rc: &ItemRc,
639 window_adapter: &Rc<dyn WindowAdapter>,
640 ) -> (bool, LogicalRect) {
641 let item = item_rc.borrow();
642
643 let item_geometry = crate::properties::evaluate_no_tracking(|| item_rc.geometry());
645
646 let rendering_data = item.cached_rendering_data_offset();
647 let mut cache = self.cache.borrow_mut();
648 let item_bounding_rect = match rendering_data.get_entry(&mut cache) {
649 Some(PartialRenderingCachedData { data, tracker: _ }) => *data.bounding_rect(),
650 None => {
651 item_rc.bounding_rect(&item_geometry, window_adapter)
653 }
654 };
655
656 let clipped_geom = self.get_current_clip().intersection(&item_bounding_rect);
657 let draw = clipped_geom.is_some_and(|clipped_geom| {
658 let clipped_geom = clipped_geom.translate(self.translation());
659 self.dirty_region.draw_intersects(clipped_geom)
660 });
661
662 (draw, item_geometry)
663 }
664
665 forward_rendering_call2!(fn draw_rectangle(dyn RenderRectangle));
666 forward_rendering_call2!(fn draw_border_rectangle(dyn RenderBorderRectangle));
667 forward_rendering_call2!(fn draw_window_background(dyn RenderRectangle));
668 forward_rendering_call2!(fn draw_image(dyn RenderImage));
669 forward_rendering_call2!(fn draw_text(dyn RenderText));
670 forward_rendering_call!(fn draw_text_input(TextInput));
671 #[cfg(feature = "std")]
672 forward_rendering_call!(fn draw_path(Path));
673 forward_rendering_call!(fn draw_box_shadow(BoxShadow));
674
675 forward_rendering_call!(fn visit_clip(Clip) -> RenderingResult);
676 forward_rendering_call!(fn visit_opacity(Opacity) -> RenderingResult);
677
678 fn combine_clip(
679 &mut self,
680 rect: LogicalRect,
681 radius: LogicalBorderRadius,
682 border_width: LogicalLength,
683 ) -> bool {
684 self.actual_renderer.combine_clip(rect, radius, border_width)
685 }
686
687 fn get_current_clip(&self) -> LogicalRect {
688 self.actual_renderer.get_current_clip()
689 }
690
691 fn translate(&mut self, distance: LogicalVector) {
692 self.actual_renderer.translate(distance)
693 }
694 fn translation(&self) -> LogicalVector {
695 self.actual_renderer.translation()
696 }
697
698 fn rotate(&mut self, angle_in_degrees: f32) {
699 self.actual_renderer.rotate(angle_in_degrees)
700 }
701
702 fn scale(&mut self, x_factor: f32, y_factor: f32) {
703 self.actual_renderer.scale(x_factor, y_factor)
704 }
705
706 fn apply_opacity(&mut self, opacity: f32) {
707 self.actual_renderer.apply_opacity(opacity)
708 }
709
710 fn save_state(&mut self) {
711 self.actual_renderer.save_state()
712 }
713
714 fn restore_state(&mut self) {
715 self.actual_renderer.restore_state()
716 }
717
718 fn scale_factor(&self) -> f32 {
719 self.actual_renderer.scale_factor()
720 }
721
722 fn draw_cached_pixmap(
723 &mut self,
724 item_rc: &ItemRc,
725 update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
726 ) {
727 self.actual_renderer.draw_cached_pixmap(item_rc, update_fn)
728 }
729
730 fn draw_string(&mut self, string: &str, color: crate::Color) {
731 self.actual_renderer.draw_string(string, color)
732 }
733
734 fn draw_image_direct(&mut self, image: crate::graphics::image::Image) {
735 self.actual_renderer.draw_image_direct(image)
736 }
737
738 fn window(&self) -> &crate::window::WindowInner {
739 self.actual_renderer.window()
740 }
741
742 fn as_any(&mut self) -> Option<&mut dyn core::any::Any> {
743 self.actual_renderer.as_any()
744 }
745}
746
747#[derive(Default)]
750pub struct PartialRenderingState {
751 partial_cache: RefCell<PartialRendererCache>,
752 force_dirty: RefCell<DirtyRegion>,
754 force_screen_refresh: Cell<bool>,
756}
757
758impl PartialRenderingState {
759 pub fn create_partial_renderer<T: ItemRenderer + ItemRendererFeatures>(
762 &self,
763 renderer: T,
764 ) -> PartialRenderer<'_, T> {
765 PartialRenderer::new(&self.partial_cache, self.force_dirty.take(), renderer)
766 }
767
768 pub fn apply_dirty_region<T: ItemRenderer + ItemRendererFeatures>(
773 &self,
774 partial_renderer: &mut PartialRenderer<'_, T>,
775 components: &[(ItemTreeWeak, LogicalPoint)],
776 logical_window_size: LogicalSize,
777 dirty_region_of_existing_buffer: Option<DirtyRegion>,
778 ) -> DirtyRegion {
779 for (component, origin) in components {
780 if let Some(component) = crate::item_tree::ItemTreeWeak::upgrade(component) {
781 partial_renderer.compute_dirty_regions(&component, *origin, logical_window_size);
782 }
783 }
784
785 let screen_region = LogicalRect::from_size(logical_window_size);
786
787 if self.force_screen_refresh.take() {
788 partial_renderer.dirty_region = screen_region.into();
789 }
790
791 let region_to_repaint = partial_renderer.dirty_region.clone();
792
793 partial_renderer.dirty_region = match dirty_region_of_existing_buffer {
794 Some(dirty_region) => partial_renderer.dirty_region.union(&dirty_region),
795 None => partial_renderer.dirty_region.clone(),
796 }
797 .intersection(screen_region);
798
799 region_to_repaint
800 }
801
802 pub fn mark_dirty_region(&self, region: DirtyRegion) {
804 self.force_dirty.replace_with(|r| r.union(®ion));
805 }
806
807 pub fn free_graphics_resources(&self, items: &mut dyn Iterator<Item = Pin<ItemRef<'_>>>) {
810 for item in items {
811 item.cached_rendering_data_offset().release(&mut self.partial_cache.borrow_mut());
812 }
813
814 self.force_screen_refresh.set(true)
817 }
818
819 pub fn clear_cache(&self) {
821 self.partial_cache.borrow_mut().clear();
822 }
823
824 pub fn force_screen_refresh(&self) {
826 self.force_screen_refresh.set(true);
827 }
828}
829
830#[test]
831fn dirty_region_no_intersection() {
832 let mut region = DirtyRegion::default();
833 region.add_rect(LogicalRect::new(LogicalPoint::new(10., 10.), LogicalSize::new(16., 16.)));
834 region.add_rect(LogicalRect::new(LogicalPoint::new(100., 100.), LogicalSize::new(16., 16.)));
835 region.add_rect(LogicalRect::new(LogicalPoint::new(200., 100.), LogicalSize::new(16., 16.)));
836 let i = region
837 .intersection(LogicalRect::new(LogicalPoint::new(50., 50.), LogicalSize::new(10., 10.)));
838 assert_eq!(i.iter().count(), 0);
839}