Skip to main content

ferroui_base/rendering/composition/drawing/
render_data_drawing_context.rs

1use super::server_resource_helper_extensions::{
2    brush_get_server, brush_render_resource, geometry_get_server, geometry_render_resource, is_immutable_brush,
3    is_immutable_pen, pen_get_client, pen_get_server, pen_render_resource,
4};
5use super::{CompositionRenderData, ICompositionRenderResource, ImmediateRenderDataSceneBrushContent, RenderDataStream};
6use crate::media::{
7    BoxShadows, EffectExtensions, Geometry, GlyphRun, IBrush, IDrawingContextCore, IEffect, IPen, ITileBrush,
8    RenderOptions, TextOptions,
9};
10use crate::platform::IGeometryImpl;
11use crate::media::imaging::BitmapBlendingMode;
12use crate::rendering::composition::Compositor;
13use crate::rendering::scene_graph::ICustomDrawOperation;
14use crate::{FerroLocator, LocatorExtensions, Matrix, Point, Rect, Ref, RoundedRect};
15use std::collections::HashSet;
16use std::rc::Rc;
17use std::sync::Arc;
18
19#[derive(Clone, Copy, Default)]
20struct PushEntry {
21    emitted: bool,
22    position_before: usize,
23    position_after: usize,
24    depth_before: i32,
25}
26
27/// A resource whose reference on the compositor is held by the render data
28/// being recorded.
29pub(crate) enum RecordedResource {
30    Brush(Rc<dyn IBrush>),
31    Pen(Rc<dyn IPen>),
32    Geometry(Ref<Geometry>),
33}
34
35impl RecordedResource {
36    pub(crate) fn with_resource<R>(&self, f: impl FnOnce(&dyn ICompositionRenderResource) -> R) -> Option<R> {
37        match self {
38            RecordedResource::Brush(brush) => brush_render_resource(&**brush).map(f),
39            RecordedResource::Pen(pen) => pen_render_resource(&**pen).map(f),
40            RecordedResource::Geometry(geometry) => geometry_render_resource(geometry).map(f),
41        }
42    }
43}
44
45/// The drawing context core that records what is drawn into render data
46/// instead of drawing it.
47///
48/// With a compositor the result is a [`CompositionRenderData`] registered
49/// for serialization to the server; without one the recorded stream refers
50/// to the drawn objects directly and is replayed on the same thread.
51pub struct RenderDataDrawingContext {
52    compositor: Option<Rc<Compositor>>,
53    stream: Option<RenderDataStream>,
54    resources: Vec<RecordedResource>,
55    resources_hash_set: HashSet<usize>,
56    push_stack: Vec<PushEntry>,
57    compositing_layers: Option<bool>,
58}
59
60impl RenderDataDrawingContext {
61    pub fn new(compositor: Option<Rc<Compositor>>) -> Self {
62        Self { compositor, stream: None, resources: Vec::new(), resources_hash_set: HashSet::new(), push_stack: Vec::new(), compositing_layers: None }
63    }
64
65    fn stream(&mut self) -> &mut RenderDataStream {
66        self.stream.get_or_insert_with(RenderDataStream::new)
67    }
68
69    fn add_brush_resource(&mut self, brush: Option<&Rc<dyn IBrush>>) {
70        let (Some(compositor), Some(brush)) = (&self.compositor, brush) else { return };
71        // Immutable brushes and composition brushes are referenced directly.
72        if is_immutable_brush(&**brush) || brush.as_composition_brush().is_some() {
73            return;
74        }
75        let Some(resource) = brush_render_resource(&**brush) else { return };
76        if !self.resources_hash_set.insert(Rc::as_ptr(brush) as *const () as usize) {
77            return;
78        }
79        resource.add_ref_on_compositor(compositor);
80        self.resources.push(RecordedResource::Brush(brush.clone()));
81    }
82
83    fn add_pen_resource(&mut self, pen: Option<&Rc<dyn IPen>>) {
84        let (Some(compositor), Some(pen)) = (&self.compositor, pen) else { return };
85        if is_immutable_pen(&**pen) {
86            return;
87        }
88        let Some(resource) = pen_render_resource(&**pen) else { return };
89        if !self.resources_hash_set.insert(Rc::as_ptr(pen) as *const () as usize) {
90            return;
91        }
92        resource.add_ref_on_compositor(compositor);
93        self.resources.push(RecordedResource::Pen(pen.clone()));
94    }
95
96    fn add_geometry_resource(&mut self, geometry: &Ref<Geometry>) {
97        let Some(compositor) = &self.compositor else { return };
98        let Some(resource) = geometry_render_resource(geometry) else { return };
99        if !self.resources_hash_set.insert(&**geometry as *const Geometry as usize) {
100            return;
101        }
102        resource.add_ref_on_compositor(compositor);
103        self.resources.push(RecordedResource::Geometry(geometry.clone()));
104    }
105
106    fn pushed_scope(&mut self, position_before: usize) {
107        let stream = self.stream();
108        let entry = PushEntry {
109            emitted: true,
110            position_before,
111            position_after: stream.opcode_length(),
112            depth_before: stream.depth() - 1,
113        };
114        self.push_stack.push(entry);
115    }
116
117    fn pushed_no_op_scope(&mut self) {
118        self.push_stack.push(PushEntry::default());
119    }
120
121    fn pop_core(&mut self) {
122        let entry = self.push_stack.pop().expect("a pop without a matching push");
123        if !entry.emitted {
124            return;
125        }
126        let stream = self.stream();
127        // Nothing was drawn inside the scope: drop the push instead of
128        // closing it.
129        if stream.opcode_length() == entry.position_after {
130            stream.rewind(entry.position_before, entry.depth_before);
131        } else {
132            stream.pop();
133        }
134    }
135
136    fn flush_stack(&mut self) {
137        while !self.push_stack.is_empty() {
138            self.pop_core();
139        }
140    }
141
142    /// Finishes the recording and returns the render data, registered for
143    /// serialization on the compositor; `None` if nothing was drawn. The
144    /// context is ready to record again.
145    ///
146    /// Panics if the context has no compositor.
147    pub fn get_render_results(&mut self) -> Option<Rc<CompositionRenderData>> {
148        let Some(compositor) = self.compositor.clone() else {
149            panic!("the drawing context records without a compositor");
150        };
151        self.flush_stack();
152
153        let resources = std::mem::take(&mut self.resources);
154        self.resources_hash_set.clear();
155        let stream = self.stream.take();
156        match stream {
157            Some(stream) if !resources.is_empty() || stream.opcode_length() > 0 => {
158                let render_data = CompositionRenderData::new(&compositor, stream, resources);
159                compositor.register_for_serialization(render_data.clone());
160                Some(render_data)
161            }
162            Some(mut stream) => {
163                stream.dispose();
164                None
165            }
166            None => None,
167        }
168    }
169
170    /// Finishes a compositor-less recording and returns what was recorded
171    /// as the content of the scene brush `brush`; `None` if nothing was
172    /// drawn. `rect` is the bounds of the content; without it the bounds of
173    /// what was drawn are used.
174    pub fn get_immediate_scene_brush_content(
175        &mut self,
176        brush: Rc<dyn ITileBrush>,
177        rect: Option<Rect>,
178        use_scalable_rasterization: bool,
179    ) -> Option<Rc<ImmediateRenderDataSceneBrushContent>> {
180        debug_assert!(self.compositor.is_none());
181        self.flush_stack();
182
183        match self.stream.take() {
184            Some(stream) if stream.opcode_length() > 0 => Some(Rc::new(ImmediateRenderDataSceneBrushContent::new(
185                brush,
186                stream,
187                rect,
188                use_scalable_rasterization,
189            ))),
190            Some(mut stream) => {
191                stream.dispose();
192                None
193            }
194            None => None,
195        }
196    }
197
198    /// Discards what has been recorded so far.
199    pub fn reset(&mut self) {
200        self.compositing_layers = None;
201        if let Some(mut stream) = self.stream.take() {
202            stream.dispose_resources();
203            stream.dispose();
204        }
205        if let Some(compositor) = &self.compositor {
206            for resource in self.resources.drain(..) {
207                resource.with_resource(|r| r.release_on_compositor(compositor));
208            }
209        }
210        self.resources.clear();
211        self.push_stack.clear();
212        self.resources_hash_set.clear();
213    }
214}
215
216impl IDrawingContextCore for RenderDataDrawingContext {
217    fn draw_line_core(&mut self, pen: &Rc<dyn IPen>, p1: Point, p2: Point) {
218        self.add_pen_resource(Some(pen));
219        let compositor = self.compositor.clone();
220        let server_pen = pen_get_server(Some(pen), compositor.as_deref());
221        let client_pen = pen_get_client(Some(pen), compositor.as_deref());
222        self.stream().draw_line(server_pen, client_pen, p1, p2);
223    }
224
225    fn draw_geometry_impl_core(
226        &mut self,
227        brush: Option<&Rc<dyn IBrush>>,
228        pen: Option<&Rc<dyn IPen>>,
229        geometry: &Arc<dyn IGeometryImpl>,
230    ) {
231        if brush.is_none() && pen.is_none() {
232            return;
233        }
234        self.add_brush_resource(brush);
235        self.add_pen_resource(pen);
236        let compositor = self.compositor.clone();
237        let server_brush = brush_get_server(brush, compositor.as_deref());
238        let server_pen = pen_get_server(pen, compositor.as_deref());
239        let client_pen = pen_get_client(pen, compositor.as_deref());
240        self.stream().draw_geometry(
241            server_brush,
242            server_pen,
243            client_pen,
244            Some(super::RenderDataResource::GeometryImpl(geometry.clone())),
245        );
246    }
247
248    fn draw_geometry_core(&mut self, brush: Option<&Rc<dyn IBrush>>, pen: Option<&Rc<dyn IPen>>, geometry: &Ref<Geometry>) {
249        if brush.is_none() && pen.is_none() {
250            return;
251        }
252        self.add_brush_resource(brush);
253        self.add_pen_resource(pen);
254        self.add_geometry_resource(geometry);
255        let compositor = self.compositor.clone();
256        let server_brush = brush_get_server(brush, compositor.as_deref());
257        let server_pen = pen_get_server(pen, compositor.as_deref());
258        let client_pen = pen_get_client(pen, compositor.as_deref());
259        let geometry = geometry_get_server(geometry, compositor.as_deref());
260        self.stream().draw_geometry(server_brush, server_pen, client_pen, geometry);
261    }
262
263    fn draw_rectangle_core(
264        &mut self,
265        brush: Option<&Rc<dyn IBrush>>,
266        pen: Option<&Rc<dyn IPen>>,
267        rrect: RoundedRect,
268        box_shadows: &BoxShadows,
269    ) {
270        if rrect.is_empty() {
271            return;
272        }
273        if brush.is_none() && pen.is_none() && *box_shadows == BoxShadows::default() {
274            return;
275        }
276        self.add_brush_resource(brush);
277        self.add_pen_resource(pen);
278        let compositor = self.compositor.clone();
279        let server_brush = brush_get_server(brush, compositor.as_deref());
280        let server_pen = pen_get_server(pen, compositor.as_deref());
281        let client_pen = pen_get_client(pen, compositor.as_deref());
282        self.stream().draw_rectangle(server_brush, server_pen, client_pen, rrect, box_shadows);
283    }
284
285    fn draw_ellipse_core(&mut self, brush: Option<&Rc<dyn IBrush>>, pen: Option<&Rc<dyn IPen>>, rect: Rect) {
286        if rect.is_empty() {
287            return;
288        }
289        if brush.is_none() && pen.is_none() {
290            return;
291        }
292        self.add_brush_resource(brush);
293        self.add_pen_resource(pen);
294        let compositor = self.compositor.clone();
295        let server_brush = brush_get_server(brush, compositor.as_deref());
296        let server_pen = pen_get_server(pen, compositor.as_deref());
297        let client_pen = pen_get_client(pen, compositor.as_deref());
298        self.stream().draw_ellipse(server_brush, server_pen, client_pen, rect);
299    }
300
301    fn draw_bitmap(&mut self, source: &crate::utilities::RefCounted<crate::platform::SharedBitmapImpl>, opacity: f64, source_rect: Rect, dest_rect: Rect) {
302        if source_rect.is_empty() || dest_rect.is_empty() {
303            return;
304        }
305        // `source.Clone()`: the record has a counted reference of its own, so the bitmap outlives a caller
306        // that disposes its reference before the compositor plays the record.
307        self.stream().draw_bitmap(Some(source.clone_ref()), opacity, source_rect, dest_rect);
308    }
309
310    fn custom(&mut self, custom: &std::sync::Arc<dyn ICustomDrawOperation>) {
311        self.stream().draw_custom(Some(custom.clone()));
312    }
313
314    fn draw_glyph_run(&mut self, foreground: Option<&Rc<dyn IBrush>>, glyph_run: &Rc<GlyphRun>) {
315        if foreground.is_none() {
316            return;
317        }
318        self.add_brush_resource(foreground);
319        let compositor = self.compositor.clone();
320        let server_brush = brush_get_server(foreground, compositor.as_deref());
321        self.stream().draw_glyph_run(server_brush, Some(glyph_run.platform_impl()));
322    }
323
324    fn push_clip_core(&mut self, rect: Rect) {
325        let before = self.stream().opcode_length();
326        self.stream().push_clip(RoundedRect::from_rect(rect));
327        self.pushed_scope(before);
328    }
329
330    fn push_rounded_clip_core(&mut self, rect: RoundedRect) {
331        let before = self.stream().opcode_length();
332        self.stream().push_clip(rect);
333        self.pushed_scope(before);
334    }
335
336    fn push_geometry_clip_core(&mut self, clip: &Ref<Geometry>) {
337        self.add_geometry_resource(clip);
338        let before = self.stream().opcode_length();
339        let compositor = self.compositor.clone();
340        let geometry = geometry_get_server(clip, compositor.as_deref());
341        self.stream().push_geometry_clip(geometry);
342        self.pushed_scope(before);
343    }
344
345    fn push_opacity_core(&mut self, opacity: f64) {
346        if opacity == 1.0 {
347            self.pushed_no_op_scope();
348            return;
349        }
350        let before = self.stream().opcode_length();
351        self.stream().push_opacity(opacity);
352        self.pushed_scope(before);
353    }
354
355    fn push_opacity_mask_core(&mut self, mask: &Rc<dyn IBrush>, bounds: Rect) {
356        self.add_brush_resource(Some(mask));
357        let before = self.stream().opcode_length();
358        let compositor = self.compositor.clone();
359        let server_brush = brush_get_server(Some(mask), compositor.as_deref());
360        self.stream().push_opacity_mask(server_brush, bounds);
361        self.pushed_scope(before);
362    }
363
364    fn push_transform_core(&mut self, matrix: Matrix) {
365        if matrix.is_identity() {
366            self.pushed_no_op_scope();
367            return;
368        }
369        let before = self.stream().opcode_length();
370        self.stream().push_transform(matrix);
371        self.pushed_scope(before);
372    }
373
374    fn push_render_options_core(&mut self, render_options: RenderOptions) {
375        let before = self.stream().opcode_length();
376        self.stream().push_render_options(render_options);
377        self.pushed_scope(before);
378    }
379
380    fn push_text_options_core(&mut self, text_options: TextOptions) {
381        let before = self.stream().opcode_length();
382        self.stream().push_text_options(text_options);
383        self.pushed_scope(before);
384    }
385
386    fn push_effect_core(&mut self, effect: &Rc<dyn IEffect>, bounds: Rect) {
387        let before = self.stream().opcode_length();
388        let padding = EffectExtensions::get_effect_output_padding(Some(&**effect));
389        let effect = EffectExtensions::to_immutable(effect);
390        self.stream().push_effect(Some(effect), bounds.inflate_thickness(padding));
391        self.pushed_scope(before);
392    }
393
394    fn try_push_compositing_layer_core(&mut self, blend: BitmapBlendingMode, opacity: f64, bounds: Rect) -> bool {
395        let supported = *self.compositing_layers.get_or_insert_with(|| {
396            FerroLocator::current().get_service::<dyn crate::platform::IPlatformRenderInterface>()
397                .is_some_and(|backend| backend.supports_compositing_layers())
398        });
399        if !supported {
400            return false;
401        }
402        let before = self.stream().opcode_length();
403        self.stream().push_compositing_layer(blend, opacity, bounds);
404        self.pushed_scope(before);
405        true
406    }
407
408    fn pop_compositing_layer_core(&mut self) {
409        self.pop_core();
410    }
411
412    fn pop_clip_core(&mut self) {
413        self.pop_core();
414    }
415
416    fn pop_geometry_clip_core(&mut self) {
417        self.pop_core();
418    }
419
420    fn pop_opacity_core(&mut self) {
421        self.pop_core();
422    }
423
424    fn pop_opacity_mask_core(&mut self) {
425        self.pop_core();
426    }
427
428    fn pop_transform_core(&mut self) {
429        self.pop_core();
430    }
431
432    fn pop_render_options_core(&mut self) {
433        self.pop_core();
434    }
435
436    fn pop_text_options_core(&mut self) {
437        self.pop_core();
438    }
439
440    fn pop_effect_core(&mut self) {
441        self.pop_core();
442    }
443
444    fn dispose_core(&mut self) {
445        self.reset();
446    }
447}