Skip to main content

ferroui_base/rendering/composition/server/
server_composition_visual_surface.rs

1use super::{
2    impl_server_render_resource, IAnimatedServerObject, IServerCompositionSurface, IServerObject,
3    IServerRenderResource, ServerCompositionSurfaceChanged, ServerCompositionVisual, ServerCompositor,
4    ServerRenderResource,
5};
6use crate::media::Colors;
7use crate::platform::{IDrawingContextImpl, IDrawingContextLayerImpl, LtrbRect};
8use crate::rendering::composition::generated::{
9    ServerCompositionVisualSurfaceHooks, ServerCompositionVisualSurfaceProps,
10};
11use crate::rendering::composition::transport::BatchStreamReader;
12use crate::{Matrix, PixelSize, Rect, Size, Vector};
13use std::any::{Any, TypeId};
14use std::cell::{Cell, RefCell};
15use std::rc::{Rc, Weak};
16use std::time::Duration;
17
18/// The server-side counterpart of a
19/// [`CompositionVisualSurface`](crate::rendering::composition::CompositionVisualSurface):
20/// a live image of the subtree of a visual.
21///
22/// The subtree is rendered where the surface is drawn, as the bitmap cache
23/// renders it: the visibility, opacity, transform and clip of the source
24/// visual itself are ignored, so a hidden source still shows. A surface
25/// that shows an ancestor of the visual that draws it would draw itself
26/// again inside that drawing; the second drawing is skipped.
27///
28/// The compositor keeps a list of the visual surfaces and, once per frame
29/// before the targets are updated, invalidates the surfaces whose source
30/// subtree changed ([`update_live_surfaces`](Self::update_live_surfaces)),
31/// which redraws the visuals that show them.
32///
33/// A frozen surface shows the image of the source taken when it was frozen:
34/// the UI-thread surface asks for the freeze with its next batch, and the
35/// compositor renders the image in the same pass ([`freeze`](Self::freeze)).
36pub struct ServerCompositionVisualSurface {
37    base: ServerRenderResource,
38    props: ServerCompositionVisualSurfaceProps,
39    changed: ServerCompositionSurfaceChanged,
40    /// Set while the source is rendered: the guard against a surface that
41    /// shows an ancestor of the visual that draws it.
42    rendering: Cell<bool>,
43    freeze_requested: Cell<bool>,
44    frozen: Cell<bool>,
45    frozen_content: RefCell<Option<FrozenContent>>,
46    /// Whether a change of the extent of the source subtree counts as a
47    /// change of the surface (the surface of a redirect visual, whose bounds
48    /// cover the source).
49    follows_source_bounds: Cell<bool>,
50    followed_source_bounds: Cell<Option<LtrbRect>>,
51}
52
53/// The image of a frozen visual surface: an offscreen layer, and the size of
54/// the source area it holds.
55struct FrozenContent {
56    layer: Rc<dyn IDrawingContextLayerImpl>,
57    size: Size,
58}
59
60impl ServerCompositionVisualSurface {
61    /// Creates the surface, active, and registers it with the compositor
62    /// for the live updates.
63    pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionVisualSurface> {
64        let surface = Rc::new_cyclic(|this: &Weak<ServerCompositionVisualSurface>| ServerCompositionVisualSurface {
65            base: ServerRenderResource::new(
66                compositor,
67                this.clone() as Weak<dyn IAnimatedServerObject>,
68                this.clone() as Weak<dyn IServerRenderResource>,
69            ),
70            props: ServerCompositionVisualSurfaceProps::new(),
71            changed: ServerCompositionSurfaceChanged::new(),
72            rendering: Cell::new(false),
73            freeze_requested: Cell::new(false),
74            frozen: Cell::new(false),
75            frozen_content: RefCell::new(None),
76            follows_source_bounds: Cell::new(false),
77            followed_source_bounds: Cell::new(None),
78        });
79        surface.base.object().activate();
80        compositor.register_visual_surface(Rc::downgrade(&surface));
81        surface
82    }
83
84    /// The properties of the surface.
85    pub fn props(&self) -> &ServerCompositionVisualSurfaceProps {
86        &self.props
87    }
88
89    pub fn is_disposed(&self) -> bool {
90        self.base.is_disposed()
91    }
92
93    /// Whether the surface is frozen.
94    pub fn is_frozen(&self) -> bool {
95        self.frozen.get()
96    }
97
98    /// The visual whose subtree the surface shows.
99    pub fn source_visual(&self) -> Option<Rc<ServerCompositionVisual>> {
100        self.props.source_visual().and_then(|visual| visual.into_any_rc().downcast::<ServerCompositionVisual>().ok())
101    }
102
103    /// The area of the source visual the surface shows, in the coordinates
104    /// of the source: `SourceOffset` and `SourceSize`, where a zero
105    /// `SourceSize` is the size of the source visual. `None` when the area
106    /// is empty or there is no source.
107    pub fn source_rect(&self) -> Option<Rect> {
108        let source = self.source_visual()?;
109        let offset = self.props.source_offset();
110        let mut size = self.props.source_size();
111        if size == Vector::default() {
112            size = source.size();
113        }
114        (size.x > 0.0 && size.y > 0.0).then(|| Rect::new(offset.x, offset.y, size.x, size.y))
115    }
116
117    /// Asks for the surface to be frozen by the next pass of the compositor
118    /// (what the batch of a UI-thread [`freeze`](crate::rendering::composition::CompositionVisualSurface::freeze)
119    /// carries).
120    pub fn request_freeze(&self) {
121        if !self.frozen.get() {
122            self.freeze_requested.set(true);
123        }
124    }
125
126    /// The surfaces that were asked to freeze are frozen, and the live
127    /// surfaces whose source changed since the last update of its target are
128    /// invalidated, which redraws the visuals that show them. Run once per
129    /// frame by the compositor, after the own properties of the visuals were
130    /// recomputed and before the targets are updated. Returns whether a
131    /// surface changed. Dropped surfaces are removed from `surfaces`.
132    pub fn update_live_surfaces(
133        compositor: &ServerCompositor,
134        surfaces: &RefCell<Vec<Weak<ServerCompositionVisualSurface>>>,
135    ) -> bool {
136        let live: Vec<Rc<ServerCompositionVisualSurface>> = {
137            let mut surfaces = surfaces.borrow_mut();
138            surfaces.retain(|surface| surface.upgrade().is_some_and(|surface| !surface.is_disposed()));
139            surfaces.iter().filter_map(Weak::upgrade).collect()
140        };
141        let mut changed = false;
142        for surface in live {
143            if surface.freeze_requested.get() {
144                // The image needs the graphics: a freeze waits for them.
145                if compositor.is_rendering_stopped() || !compositor.render_interface().is_ready() {
146                    continue;
147                }
148                surface.freeze(compositor);
149                changed = true;
150            } else if surface.source_changed() {
151                surface.content_changed();
152                changed = true;
153            }
154        }
155        changed
156    }
157
158    /// Whether the source subtree changed since the last update of the
159    /// target it is attached to. A source that is not attached to a target
160    /// is not updated, so its changes are not followed.
161    fn source_changed(&self) -> bool {
162        if self.frozen.get() {
163            return false;
164        }
165        let Some(source) = self.source_visual() else { return false };
166        if source.root().is_none() {
167            return false;
168        }
169        let mut changed = source.has_pending_render_changes();
170        if self.follows_source_bounds.get() {
171            // The bounds of the last update of the target, which the
172            // pending changes of the last frame may have moved.
173            let bounds = source.unclipped_sub_tree_bounds();
174            if self.followed_source_bounds.replace(bounds) != bounds {
175                changed = true;
176            }
177        }
178        changed
179    }
180
181    /// Makes a change of the extent of the source subtree a change of the
182    /// surface (what a redirect visual, whose bounds cover its source, needs;
183    /// not from upstream).
184    pub fn set_follows_source_bounds(&self, value: bool) {
185        self.follows_source_bounds.set(value);
186    }
187
188    /// Renders the whole subtree of the source onto `canvas`, whose transform
189    /// maps the coordinates of the source, as the bitmap cache renders it
190    /// (the visibility, opacity, transform and clip of the source itself are
191    /// ignored), unless the surface is rendering its source already: what a
192    /// redirect visual draws (not from upstream). `clip` is in the
193    /// coordinates of the device.
194    pub fn render_source_subtree(&self, canvas: &mut dyn IDrawingContextImpl, clip: LtrbRect) {
195        let Some(source) = self.source_visual() else { return };
196        self.render_source(&source, canvas, clip);
197    }
198
199    fn content_changed(&self) {
200        self.base.core().invalidated(self);
201        self.changed.invoke();
202    }
203
204    /// Renders the source into an offscreen layer once, and shows that layer
205    /// from now on (not a public member in WinUI; the connected animation
206    /// freezes the image of its source). Runs on the render thread, with the
207    /// bounds of the source subtree as of the last frame: best effort, a
208    /// source that was never rendered in a target has no bounds for its
209    /// children yet.
210    pub fn freeze(&self, compositor: &ServerCompositor) {
211        self.freeze_requested.set(false);
212        if self.frozen.replace(true) {
213            return;
214        }
215        let (Some(source), Some(rect)) = (self.source_visual(), self.source_rect()) else {
216            self.content_changed();
217            return;
218        };
219        let scaling = source.root().map(|root| root.scaling()).filter(|scaling| *scaling > 0.0).unwrap_or(1.0);
220        let size = Size::new(rect.width, rect.height);
221        let pixel_size = PixelSize::from_size_ceiling(size, scaling);
222        if pixel_size.width < 1 || pixel_size.height < 1 {
223            self.content_changed();
224            return;
225        }
226
227        let current = compositor.render_interface().ensure_current();
228        let layer = compositor.render_interface().value().create_offscreen_render_target(
229            pixel_size,
230            Vector::new(scaling, scaling),
231            false,
232        );
233        {
234            let mut canvas = layer.create_drawing_context();
235            canvas.clear(Colors::TRANSPARENT);
236            canvas.set_transform(
237                Matrix::create_translation(-rect.x, -rect.y) * Matrix::create_scale(scaling, scaling),
238            );
239            self.render_source(&source, &mut *canvas, LtrbRect::INFINITE);
240            canvas.dispose();
241        }
242        current.dispose();
243        *self.frozen_content.borrow_mut() = Some(FrozenContent { layer, size });
244        self.content_changed();
245    }
246
247    /// Renders the subtree of `source` onto `canvas` (whose transform maps
248    /// the coordinates of the source), unless it is being rendered already.
249    fn render_source(&self, source: &Rc<ServerCompositionVisual>, canvas: &mut dyn IDrawingContextImpl, clip: LtrbRect) {
250        if self.rendering.replace(true) {
251            return;
252        }
253        // The root of a bitmap-cache walk skips its own transform already; asking to skip it as
254        // well would leave the request to the first child.
255        source.render(canvas, clip, None, true, false, true);
256        self.rendering.set(false);
257    }
258
259    /// Whether the source is being rendered by this surface: the drawing of
260    /// the surface inside it is skipped.
261    pub fn is_rendering(&self) -> bool {
262        self.rendering.get()
263    }
264
265    fn free_frozen_content(&self) {
266        if let Some(content) = self.frozen_content.borrow_mut().take() {
267            content.layer.dispose();
268        }
269    }
270}
271
272impl_server_render_resource!(
273    ServerCompositionVisualSurface,
274    base,
275    composition_property: ServerCompositionVisualSurfaceProps::get_composition_property
276);
277
278impl ServerCompositionVisualSurfaceHooks for ServerCompositionVisualSurface {
279    /// The request to freeze, written by the UI-thread surface before its
280    /// properties.
281    fn deserialize_changes_extra(&self, reader: &mut BatchStreamReader<'_>) {
282        if reader.read::<bool>() {
283            self.request_freeze();
284        }
285    }
286}
287
288impl IServerObject for ServerCompositionVisualSurface {
289    fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
290        self.props.deserialize_changes_core(self, reader, committed_at);
291    }
292
293    fn values_invalidated(&self) {
294        self.content_changed();
295    }
296
297    fn dispose(&self) {
298        self.free_frozen_content();
299        self.base.core().dispose();
300    }
301
302    fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
303        Some(self)
304    }
305
306    fn as_surface(self: Rc<Self>) -> Option<Rc<dyn IServerCompositionSurface>> {
307        Some(self)
308    }
309
310    fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
311        Some(self)
312    }
313
314    fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
315        self.props.find_props(type_id)
316    }
317
318    fn as_any(&self) -> &dyn Any {
319        self
320    }
321
322    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
323        self
324    }
325}
326
327impl IServerCompositionSurface for ServerCompositionVisualSurface {
328    /// A visual surface has no bitmap: its content is drawn by
329    /// [`draw`](IServerCompositionSurface::draw).
330    fn bitmap(&self) -> Option<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
331        None
332    }
333
334    fn changed(&self) -> &ServerCompositionSurfaceChanged {
335        &self.changed
336    }
337
338    fn natural_size(&self) -> Option<Size> {
339        if let Some(content) = self.frozen_content.borrow().as_ref() {
340            return Some(content.size);
341        }
342        if self.frozen.get() {
343            return None;
344        }
345        self.source_rect().map(|rect| Size::new(rect.width, rect.height))
346    }
347
348    /// Draws the source area of the source visual into `dest`: the frozen
349    /// image, or the subtree rendered with the transform that maps the
350    /// source area onto `dest`, clipped to `dest`.
351    fn draw(&self, canvas: &mut dyn IDrawingContextImpl, dest: Rect, opacity: f64) {
352        if dest.width <= 0.0 || dest.height <= 0.0 {
353            return;
354        }
355        if let Some(content) = self.frozen_content.borrow().as_ref() {
356            let pixel_size = content.layer.pixel_size();
357            canvas.draw_bitmap(
358                &*content.layer,
359                opacity,
360                Rect::new(0.0, 0.0, pixel_size.width as f64, pixel_size.height as f64),
361                dest,
362            );
363            return;
364        }
365        if self.frozen.get() || self.rendering.get() {
366            return;
367        }
368        let (Some(source), Some(rect)) = (self.source_visual(), self.source_rect()) else { return };
369
370        let saved = canvas.transform();
371        canvas.push_clip(dest);
372        if opacity != 1.0 {
373            canvas.push_opacity(opacity, Some(dest));
374        }
375        let map = Matrix::create_translation(-rect.x, -rect.y)
376            * Matrix::create_scale(dest.width / rect.width, dest.height / rect.height)
377            * Matrix::create_translation(dest.x, dest.y);
378        canvas.set_transform(map * saved);
379        let clip = LtrbRect::from_rect(dest).transform_to_aabb(saved);
380        self.render_source(&source, canvas, clip);
381        canvas.set_transform(saved);
382        if opacity != 1.0 {
383            canvas.pop_opacity();
384        }
385        canvas.pop_clip();
386    }
387}