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