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 | |
7 | use super::graphics::RenderingCache; |
8 | use super::items::*; |
9 | use crate::graphics::{CachedGraphicsData, FontRequest, Image, IntRect}; |
10 | use crate::item_tree::ItemTreeRc; |
11 | use crate::item_tree::{ItemVisitor, ItemVisitorResult, ItemVisitorVTable, VisitChildrenResult}; |
12 | use crate::lengths::{ |
13 | ItemTransform, LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalPx, LogicalRect, |
14 | LogicalSize, LogicalVector, SizeLengths, |
15 | }; |
16 | use crate::properties::PropertyTracker; |
17 | use crate::window::{WindowAdapter, WindowInner}; |
18 | use crate::{Brush, Coord, SharedString}; |
19 | use alloc::boxed::Box; |
20 | use alloc::rc::Rc; |
21 | use core::cell::{Cell, RefCell}; |
22 | use core::pin::Pin; |
23 | #[cfg (feature = "std" )] |
24 | use std::collections::HashMap; |
25 | use 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)] |
31 | pub 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 | |
40 | impl 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" )] |
77 | pub 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" )] |
85 | impl<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" )] |
92 | impl<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. |
185 | pub 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. |
233 | pub 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`. |
249 | pub 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)] |
287 | pub 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)] |
293 | pub 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)] |
302 | pub 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)] |
315 | pub 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)] |
357 | pub 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. |
528 | pub 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 | |
537 | pub 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 | |
559 | impl 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 |
614 | pub type PartialRenderingCache = RenderingCache<CachedItemBoundingBoxAndTransform>; |
615 | |
616 | /// A region composed of a few rectangles that need to be redrawn. |
617 | #[derive (Default, Clone, Debug)] |
618 | pub struct DirtyRegion { |
619 | rectangles: [euclid::Box2D<Coord, LogicalPx>; Self::MAX_COUNT], |
620 | count: usize, |
621 | } |
622 | |
623 | impl 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 | |
723 | impl 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)] |
734 | pub 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 |
751 | pub 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 | |
761 | impl<'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 | |
981 | macro_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 | |
994 | macro_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 | |
1007 | impl<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)] |
1131 | pub 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 | |
1139 | impl 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(®ion)); |
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 ] |
1210 | fn 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 | |