Skip to main content

ferroui_base/rendering/composition/server/
server_composition_target.rs

1use super::{
2    impl_animated_server_object, CompositionTargetOverlays, DebugEventsDirtyRectCollectorProxy, IAnimatedServerObject,
3    IDirtyRectCollector, IDirtyRectTracker, IServerObject, MultiDirtyRectTracker, RegionDirtyRectTracker,
4    ServerCompositionVisual, ServerCompositor, ServerObject, SingleDirtyRectTracker,
5};
6use crate::media::Colors;
7use crate::platform::surfaces::IPlatformRenderSurface;
8use crate::platform::{
9    IDrawingContextImpl, IDrawingContextLayerImpl, IRenderTarget, LtrbRect, RenderTargetError, RenderTargetSceneInfo,
10};
11use crate::rendering::composition::generated::{
12    CompositionTargetChangedFields, ServerCompositionTargetHooks, ServerCompositionTargetProps,
13};
14use crate::rendering::composition::transport::BatchStreamReader;
15use crate::rendering::composition::{CompositionTransparencyLevel, ICompositionTargetDebugEvents};
16use crate::rendering::{LayoutPassTiming, RendererDebugOverlays};
17use crate::{Matrix, PixelRect, PixelSize, Size};
18use std::any::{Any, TypeId};
19use std::cell::{Cell, RefCell};
20use std::collections::HashMap;
21use std::sync::Arc;
22use std::rc::{Rc, Weak};
23use std::time::Duration;
24
25/// Returns the current platform surfaces of a composition target.
26///
27/// The thread that renders calls it and uses what it returns: the function
28/// and the surfaces are shared between the threads.
29pub type RenderSurfaces = std::sync::Arc<dyn Fn() -> Vec<std::sync::Arc<dyn IPlatformRenderSurface>> + Send + Sync>;
30
31/// Server-side counterpart of the `CompositionTarget`. That's the place
32/// where we update visual transforms, track dirty rects and actually do
33/// rendering.
34pub struct ServerCompositionTarget {
35    this: Weak<ServerCompositionTarget>,
36    object: ServerObject,
37    compositor: Weak<ServerCompositor>,
38    props: ServerCompositionTargetProps,
39    surfaces: RenderSurfaces,
40    overlays: CompositionTargetOverlays,
41    render_target: RefCell<Option<Rc<dyn IRenderTarget>>>,
42    layer_size: Cell<PixelSize>,
43    layer: RefCell<Option<Rc<dyn IDrawingContextLayerImpl>>>,
44    update_requested: Cell<bool>,
45    redraw_requested: Cell<bool>,
46    full_redraw_requested: Cell<bool>,
47    disposed: Cell<bool>,
48    attached_visuals: RefCell<HashMap<*const ServerCompositionVisual, Rc<ServerCompositionVisual>>>,
49    dirty_rects: Rc<dyn IDirtyRectTracker>,
50    id: i64,
51    debug_events: RefCell<Option<Arc<dyn ICompositionTargetDebugEvents>>>,
52    rendered_visuals: Cell<i32>,
53    visited_visuals: Cell<i32>,
54    is_waiting_for_ready_render_target: Cell<bool>,
55    is_waiting_for_render_loop_wakeup: Cell<bool>,
56    /// The times of the frames the target drew, when the environment asks
57    /// for them (`FERROUI_FRAME_TIMES=1`): a summary is printed to the
58    /// standard error when the target is disposed. Not from the reference: a
59    /// diagnostic that measures render backends in a window on screen
60    /// (docs/porting/vello-backend.md, section 14).
61    frame_times: RefCell<Option<Vec<std::time::Duration>>>,
62    /// Whether the render that is running drew a frame.
63    drew_frame: Cell<bool>,
64    /// The backdrops drawn in the frames so far (winui-materials.md): the
65    /// visual that painted each and its area in device pixels. A dirty
66    /// rectangle that touches one makes all of it dirty.
67    backdrop_regions: RefCell<Vec<(Weak<ServerCompositionVisual>, LtrbRect)>>,
68}
69
70impl ServerCompositionTarget {
71    /// Whether the target has been disposed. A disposed target stays in
72    /// the table of the compositor until its id is released.
73    pub(crate) fn is_disposed(&self) -> bool {
74        self.disposed.get()
75    }
76
77    /// `id` is the identity of the target, allocated by the UI thread so
78    /// that it can match readback data against it.
79    pub fn new(compositor: &Rc<ServerCompositor>, surfaces: RenderSurfaces, id: i64) -> Rc<ServerCompositionTarget> {
80        let mut dirty_rects: Option<Rc<dyn IDirtyRectTracker>> = None;
81        // Not the service locator: a target is created by the thread that
82        // applies the batch. The render interface is lent for the call.
83        compositor.render_interface().with_platform_render_interface(|platform_render| {
84            if platform_render.supports_regions() && compositor.options().use_region_dirty_rect_clipping == Some(true) {
85                let max_rects = compositor.options().max_dirty_rects.unwrap_or(8);
86                dirty_rects = Some(if max_rects <= 0 {
87                    Rc::new(RegionDirtyRectTracker::new(platform_render))
88                } else {
89                    Rc::new(MultiDirtyRectTracker::new(
90                        platform_render,
91                        max_rects,
92                        // WPF uses 50K, but that merges stuff rather aggressively
93                        compositor.options().dirty_rect_merge_eagerness.unwrap_or(1000.0),
94                    ))
95                });
96            }
97        });
98        let dirty_rects = dirty_rects.unwrap_or_else(|| Rc::new(SingleDirtyRectTracker::new()));
99
100        Rc::new_cyclic(|this: &Weak<ServerCompositionTarget>| {
101            let owner: Weak<dyn IAnimatedServerObject> = this.clone();
102            ServerCompositionTarget {
103                this: this.clone(),
104                object: ServerObject::new(compositor, owner),
105                compositor: Rc::downgrade(compositor),
106                props: ServerCompositionTargetProps::new(),
107                surfaces,
108                overlays: CompositionTargetOverlays::new(compositor),
109                render_target: RefCell::new(None),
110                layer_size: Cell::new(PixelSize::default()),
111                layer: RefCell::new(None),
112                update_requested: Cell::new(false),
113                redraw_requested: Cell::new(false),
114                full_redraw_requested: Cell::new(false),
115                disposed: Cell::new(false),
116                attached_visuals: RefCell::new(HashMap::new()),
117                dirty_rects,
118                id,
119                debug_events: RefCell::new(None),
120                rendered_visuals: Cell::new(0),
121                visited_visuals: Cell::new(0),
122                is_waiting_for_ready_render_target: Cell::new(false),
123                is_waiting_for_render_loop_wakeup: Cell::new(false),
124                frame_times: RefCell::new(
125                    std::env::var("FERROUI_FRAME_TIMES").is_ok_and(|value| value == "1").then(Vec::new),
126                ),
127                drew_frame: Cell::new(false),
128                backdrop_regions: RefCell::new(Vec::new()),
129            }
130        })
131    }
132
133    /// Makes dirty the whole of every backdrop a dirty rectangle touches,
134    /// until no more is added: a blurred backdrop drawn in part would mix the
135    /// new pixels with the old ones (winui-materials.md, the backdrop).
136    fn expand_dirty_rects_over_backdrops(&self) {
137        let mut regions = self.backdrop_regions.borrow_mut();
138        regions.retain(|(visual, _)| visual.upgrade().is_some_and(|visual| visual.root().is_some()));
139        if regions.is_empty() || self.dirty_rects.is_empty() {
140            return;
141        }
142        let mut added = vec![false; regions.len()];
143        loop {
144            let mut changed = false;
145            for (index, (_, region)) in regions.iter().enumerate() {
146                if !added[index] && self.dirty_rects.intersects(*region) {
147                    self.dirty_rects.add_rect(*region);
148                    added[index] = true;
149                    changed = true;
150                }
151            }
152            if !changed {
153                break;
154            }
155        }
156    }
157
158    /// Keeps the backdrops drawn in the frame that just ended, replacing the
159    /// areas of the visuals that drew again.
160    fn record_backdrop_regions(&self) {
161        let drawn = super::take_backdrop_regions();
162        if drawn.is_empty() {
163            return;
164        }
165        let mut regions = self.backdrop_regions.borrow_mut();
166        for (visual, region) in drawn {
167            regions.retain(|(existing, _)| !existing.ptr_eq(&visual));
168            regions.push((visual, region));
169        }
170    }
171
172    pub fn compositor(&self) -> Option<Rc<ServerCompositor>> {
173        self.compositor.upgrade()
174    }
175
176    pub fn dirty_rects(&self) -> &Rc<dyn IDirtyRectTracker> {
177        &self.dirty_rects
178    }
179
180    pub fn id(&self) -> i64 {
181        self.id
182    }
183
184    pub fn debug_events(&self) -> Option<Arc<dyn ICompositionTargetDebugEvents>> {
185        self.debug_events.borrow().clone()
186    }
187
188    pub fn set_debug_events(&self, value: Option<Arc<dyn ICompositionTargetDebugEvents>>) {
189        *self.debug_events.borrow_mut() = value;
190    }
191
192    pub fn rendered_visuals(&self) -> i32 {
193        self.rendered_visuals.get()
194    }
195
196    pub fn visited_visuals(&self) -> i32 {
197        self.visited_visuals.get()
198    }
199
200    pub fn pixel_size(&self) -> PixelSize {
201        PixelSize::from_size_ceiling(self.size(), self.scaling())
202    }
203
204    /// Returns true if the target is enabled and has pending work but its
205    /// render target was not ready.
206    pub fn is_waiting_for_ready_render_target(&self) -> bool {
207        self.is_waiting_for_ready_render_target.get()
208    }
209
210    /// Returns true if the target's render target is waiting for a render
211    /// loop wakeup (i.e. the platform will call Wakeup() when ready, no
212    /// need to keep polling).
213    pub fn is_waiting_for_render_loop_wakeup(&self) -> bool {
214        self.is_waiting_for_render_loop_wakeup.get()
215    }
216
217    // --- generated properties -------------------------------------------------
218
219    pub fn root(&self) -> Option<Rc<ServerCompositionVisual>> {
220        self.props.root().and_then(|o| o.into_any_rc().downcast::<ServerCompositionVisual>().ok())
221    }
222
223    pub fn is_enabled(&self) -> bool {
224        self.props.is_enabled()
225    }
226
227    pub fn debug_overlays(&self) -> RendererDebugOverlays {
228        self.props.debug_overlays()
229    }
230
231    pub fn last_layout_pass_timing(&self) -> LayoutPassTiming {
232        self.props.last_layout_pass_timing()
233    }
234
235    pub fn scaling(&self) -> f64 {
236        self.props.scaling()
237    }
238
239    pub fn size(&self) -> Size {
240        self.props.size()
241    }
242
243    pub fn transparency_level(&self) -> CompositionTransparencyLevel {
244        self.props.transparency_level()
245    }
246
247    pub fn platform_specific_scene_info(&self) -> Option<std::sync::Arc<dyn Any + Send + Sync>> {
248        self.props.platform_specific_scene_info()
249    }
250
251    // ---------------------------------------------------------------------------
252
253    pub fn update(self: &Rc<Self>, diagnostics_compositor_global_update_elapsed_time: Duration) {
254        let Some(compositor) = self.compositor() else { return };
255        if self.disposed.get() {
256            compositor.remove_composition_target(self);
257            return;
258        }
259
260        let Some(root) = self.root() else { return };
261
262        self.overlays.record_global_compositor_update_time(self, diagnostics_compositor_global_update_elapsed_time);
263        self.overlays.mark_update_call_start(self);
264
265        let scaling = self.scaling();
266        let transform = Matrix::create_scale(scaling, scaling);
267        let tracker: Rc<dyn IDirtyRectCollector> = self.dirty_rects.clone();
268        let collector: Rc<dyn IDirtyRectCollector> = match self.debug_events() {
269            Some(events) => Rc::new(DebugEventsDirtyRectCollectorProxy::new(tracker, events)),
270            None => tracker,
271        };
272        let pixel_size = self.pixel_size();
273        root.update_root(
274            collector,
275            transform,
276            LtrbRect::new(0.0, 0.0, pixel_size.width as f64, pixel_size.height as f64),
277        );
278        self.update_requested.set(false);
279        self.overlays.mark_update_call_end(self);
280    }
281
282    pub fn render(self: &Rc<Self>) {
283        if self.frame_times.borrow().is_none() {
284            return self.render_frame();
285        }
286        self.drew_frame.set(false);
287        let start = std::time::Instant::now();
288        self.render_frame();
289        if self.drew_frame.get() {
290            if let Some(times) = self.frame_times.borrow_mut().as_mut() {
291                times.push(start.elapsed());
292            }
293        }
294    }
295
296    /// The times of the frames the target drew, as a line of text: their
297    /// count, median, mean, 95th percentile and longest.
298    fn frame_times_summary(&self) -> Option<String> {
299        let mut times = self.frame_times.borrow().clone()?;
300        if times.is_empty() {
301            return None;
302        }
303        times.sort();
304        let ms = |duration: std::time::Duration| duration.as_secs_f64() * 1000.0;
305        let mean = times.iter().map(|time| ms(*time)).sum::<f64>() / times.len() as f64;
306        Some(format!(
307            "frame times of composition target {}: {} frames; ms per frame: median {:.3}, mean {:.3}, p95 {:.3}, max {:.3}",
308            self.id,
309            times.len(),
310            ms(times[times.len() / 2]),
311            mean,
312            ms(times[(times.len() * 95 / 100).min(times.len() - 1)]),
313            ms(times[times.len() - 1]),
314        ))
315    }
316
317    fn render_frame(self: &Rc<Self>) {
318        self.is_waiting_for_ready_render_target.set(false);
319        self.is_waiting_for_render_loop_wakeup.set(false);
320
321        if self.disposed.get() {
322            return;
323        }
324
325        let Some(root) = self.root() else { return };
326        let Some(compositor) = self.compositor() else { return };
327
328        let corrupted = self.render_target.borrow().as_ref().is_some_and(|t| t.platform_render_target_state().is_corrupted);
329        if corrupted {
330            if let Some(layer) = self.layer.borrow_mut().take() {
331                layer.dispose();
332            }
333            if let Some(render_target) = self.render_target.borrow_mut().take() {
334                render_target.dispose();
335            }
336            self.redraw_requested.set(true);
337        }
338
339        if self.render_target.borrow().is_none() {
340            let surfaces = (self.surfaces)();
341            if !compositor.is_ready_to_create_render_target(&surfaces) {
342                self.is_waiting_for_ready_render_target.set(self.is_enabled());
343                return;
344            }
345            let render_target = compositor.create_render_target(&surfaces);
346            *self.render_target.borrow_mut() = Some(render_target);
347        }
348        let Some(render_target) = self.render_target.borrow().clone() else { return };
349
350        if self.dirty_rects.is_empty() && !self.redraw_requested.get() && !self.update_requested.get() {
351            return;
352        }
353
354        self.redraw_requested.set(self.redraw_requested.get() | !self.dirty_rects.is_empty());
355
356        if !self.redraw_requested.get() {
357            return;
358        }
359
360        let state = render_target.platform_render_target_state();
361        if !state.is_ready {
362            self.is_waiting_for_ready_render_target.set(self.is_enabled());
363            self.is_waiting_for_render_loop_wakeup
364                .set(self.is_enabled() && state.will_wake_up_render_loop_when_ready);
365            return;
366        }
367
368        let properties = render_target.properties();
369        let need_layer = self.overlays.require_layer() // Check if we don't need overlays
370            // Check if render target can be rendered to directly and preserves the previous frame
371            || !(properties.retains_previous_frame_contents && properties.is_suitable_for_direct_rendering);
372
373        let pixel_size = self.pixel_size();
374        let scene_info = RenderTargetSceneInfo {
375            size: pixel_size,
376            scaling: self.scaling(),
377            logical_size: self.size(),
378            transparency_level: self.transparency_level(),
379            platform_specific_scene_info: self.platform_specific_scene_info(),
380        };
381        self.drew_frame.set(true);
382        let (mut render_target_context, properties) = match render_target.try_create_drawing_context(&scene_info) {
383            Ok(created) => created,
384            Err(RenderTargetError::NotReady(_)) => {
385                self.is_waiting_for_ready_render_target.set(self.is_enabled());
386                return;
387            }
388            Err(RenderTargetError::Corrupted(_)) => return,
389        };
390
391        {
392            let mut full_redraw = false;
393
394            let layer_is_stale = {
395                let layer = self.layer.borrow();
396                pixel_size != self.layer_size.get() || layer.as_ref().is_none_or(|layer| layer.is_corrupted())
397            };
398            if need_layer && layer_is_stale {
399                if let Some(layer) = self.layer.borrow_mut().take() {
400                    layer.dispose();
401                }
402                *self.layer.borrow_mut() = Some(render_target_context.create_layer(pixel_size));
403                self.layer_size.set(pixel_size);
404                full_redraw = true;
405            } else if !need_layer {
406                if let Some(layer) = self.layer.borrow_mut().take() {
407                    layer.dispose();
408                }
409            }
410
411            if self.full_redraw_requested.get() || (!need_layer && !properties.previous_frame_is_retained) {
412                self.full_redraw_requested.set(false);
413                full_redraw = true;
414            }
415
416            let render_bounds = LtrbRect::new(0.0, 0.0, pixel_size.width as f64, pixel_size.height as f64);
417
418            if full_redraw {
419                self.dirty_rects.initialize(render_bounds);
420                self.dirty_rects.add_rect(render_bounds);
421            }
422
423            self.expand_dirty_rects_over_backdrops();
424            if !self.dirty_rects.is_empty() {
425                super::take_backdrop_regions();
426                self.dirty_rects.finalize_frame(render_bounds);
427                let layer = self.layer.borrow().clone();
428                match layer {
429                    Some(layer) => {
430                        {
431                            let mut context = layer.create_drawing_context();
432                            self.render_root_to_context_with_clip(&compositor, &mut *context, &root);
433                            context.dispose();
434                        }
435
436                        render_target_context.clear(Colors::TRANSPARENT);
437                        render_target_context.set_transform(Matrix::IDENTITY);
438                        if layer.can_blit() {
439                            layer.blit(&mut *render_target_context);
440                        } else {
441                            let rect = PixelRect::from_size(pixel_size).to_rect(1.0);
442                            render_target_context.draw_bitmap(&*layer, 1.0, rect, rect);
443                        }
444                        self.overlays.draw(self, &mut *render_target_context, true);
445                    }
446                    None => {
447                        self.render_root_to_context_with_clip(&compositor, &mut *render_target_context, &root);
448                        self.overlays.draw(self, &mut *render_target_context, false);
449                    }
450                }
451                self.record_backdrop_regions();
452            }
453
454            self.rendered_visuals.set(0);
455            self.visited_visuals.set(0);
456
457            self.redraw_requested.set(false);
458            self.dirty_rects.initialize(render_bounds);
459        }
460        render_target_context.dispose();
461    }
462
463    fn render_root_to_context_with_clip(
464        &self,
465        compositor: &ServerCompositor,
466        context: &mut dyn IDrawingContextImpl,
467        root: &Rc<ServerCompositionVisual>,
468    ) {
469        let use_layer_clip = compositor.options().use_save_layer_root_clip.unwrap_or(false);
470        self.dirty_rects.begin_draw(context);
471        {
472            context.clear(Colors::TRANSPARENT);
473            if use_layer_clip {
474                context.push_layer(self.dirty_rects.combined_rect().to_rect());
475            }
476            let scaling = self.scaling();
477            context.set_transform(Matrix::create_scale(scaling, scaling));
478            let pixel_size = self.pixel_size();
479            let (visited, rendered) = root.render(
480                context,
481                LtrbRect::new(0.0, 0.0, pixel_size.width as f64, pixel_size.height as f64),
482                Some(self.dirty_rects.clone()),
483                true,
484                false,
485                false,
486            );
487            self.visited_visuals.set(visited);
488            self.rendered_visuals.set(rendered);
489            if let Some(events) = self.debug_events() {
490                events.set_rendered_visuals(rendered);
491                events.set_visited_visuals(visited);
492            }
493
494            if use_layer_clip {
495                context.pop_layer();
496            }
497        }
498        self.dirty_rects.end_draw(context);
499    }
500
501    pub fn request_update(&self) {
502        self.update_requested.set(true);
503    }
504
505    pub fn reset_render_target(&self) {
506        if self.layer.borrow().is_none() && self.render_target.borrow().is_none() {
507            return;
508        }
509        let current = self.compositor().map(|compositor| compositor.render_interface().ensure_current());
510        self.release_render_target();
511        if let Some(current) = current {
512            current.dispose();
513        }
514    }
515
516    /// Releases the layer and the render target of the target, in the
517    /// graphics context that is current: the caller has made the context of
518    /// the compositor current, without asking for a backend context (see
519    /// [`ServerCompositor::release_gpu_resources`]).
520    pub(crate) fn release_render_target(&self) {
521        if let Some(layer) = self.layer.borrow_mut().take() {
522            layer.dispose();
523        }
524        if let Some(render_target) = self.render_target.borrow_mut().take() {
525            render_target.dispose();
526        }
527    }
528
529    pub fn add_visual(&self, visual: &Rc<ServerCompositionVisual>) {
530        let added = self.attached_visuals.borrow_mut().insert(Rc::as_ptr(visual), visual.clone()).is_none();
531        if added && self.is_enabled() {
532            visual.activate();
533        }
534    }
535
536    pub fn remove_visual(&self, visual: &Rc<ServerCompositionVisual>) {
537        let removed = self.attached_visuals.borrow_mut().remove(&Rc::as_ptr(visual)).is_some();
538        if removed && self.is_enabled() {
539            visual.deactivate();
540        }
541    }
542
543    pub fn request_full_redraw(&self) {
544        self.redraw_requested.set(true);
545    }
546}
547
548impl ServerCompositionTargetHooks for ServerCompositionTarget {
549    fn deserialize_changes_extra(&self, _reader: &mut BatchStreamReader<'_>) {
550        self.redraw_requested.set(true);
551        self.full_redraw_requested.set(true);
552    }
553
554    fn on_fields_deserialized(&self, _changed: CompositionTargetChangedFields) {}
555
556    fn on_is_enabled_changed(&self) {
557        let (Some(compositor), Some(this)) = (self.compositor(), self.this.upgrade()) else { return };
558        let visuals: Vec<_> = self.attached_visuals.borrow().values().cloned().collect();
559        if self.is_enabled() {
560            compositor.add_composition_target(&this);
561            for visual in visuals {
562                visual.activate();
563            }
564        } else {
565            compositor.remove_composition_target(&this);
566            for visual in visuals {
567                visual.deactivate();
568            }
569        }
570    }
571
572    fn on_debug_overlays_changed(&self) {
573        self.full_redraw_requested.set(true);
574        self.overlays.on_changed(self.debug_overlays());
575    }
576
577    fn on_last_layout_pass_timing_changed(&self) {
578        self.overlays.on_last_layout_pass_timing_changed(self, self.last_layout_pass_timing());
579    }
580}
581
582impl_animated_server_object!(ServerCompositionTarget, object);
583
584impl IServerObject for ServerCompositionTarget {
585    fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
586        self.props.deserialize_changes_core(self, reader, committed_at);
587    }
588
589    fn dispose(&self) {
590        if self.disposed.replace(true) {
591            return;
592        }
593        if let Some(summary) = self.frame_times_summary() {
594            eprintln!("{summary}");
595        }
596        self.reset_render_target();
597        if let (Some(compositor), Some(this)) = (self.compositor(), self.this.upgrade()) {
598            compositor.remove_composition_target(&this);
599        }
600        // The garbage collector reclaims the target and its tree upstream;
601        // here the references that form cycles are dropped.
602        self.attached_visuals.borrow_mut().clear();
603        ServerCompositionTargetProps::id_of_root_property().set_field(self, None);
604    }
605
606    fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
607        self.props.find_props(type_id)
608    }
609
610    fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
611        Some(self)
612    }
613
614    fn as_any(&self) -> &dyn Any {
615        self
616    }
617
618    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
619        self
620    }
621}