Skip to main content

ferroui_vello/
surface_render_target.rs

1use crate::drawing_context_impl::{CreateInfo, DrawingContextImpl, VelloBackdrop};
2use crate::helpers::image_saving_helper;
3use crate::helpers::pixel_format_helper::{scene_size, to_shared_image};
4use crate::i_drawable_bitmap_impl::IDrawableBitmapImpl;
5use crate::immutable_bitmap::ImmutableBitmap;
6use crate::scene::{create_scene_sink, VelloSceneBrush, VelloSceneImage};
7use crate::vello_options::VelloRenderingMode;
8use ferroui_base::media::imaging::BitmapEncoderOptions;
9use ferroui_base::platform::{IBitmapImpl, IDrawingContextImpl, IDrawingContextLayerImpl, RenderTargetProperties};
10use ferroui_base::{PixelSize, Vector};
11use peniko::{BlendMode, Extend, ImageData, ImageQuality};
12use std::any::Any;
13use std::cell::{Cell, RefCell};
14use std::io::{self, Write};
15use std::rc::Rc;
16
17/// Create info of a [`SurfaceRenderTarget`].
18#[derive(Clone, Default)]
19pub struct SurfaceRenderTargetCreateInfo {
20    /// Width of a render target.
21    pub width: i32,
22
23    /// Height of a render target.
24    pub height: i32,
25
26    /// Dpi used when rendering to a surface.
27    pub dpi: Vector,
28
29    /// The rendering modes in the order they are tried.
30    pub rendering_modes: Vec<VelloRenderingMode>,
31
32    /// Apply the DPI as a hidden transform when drawing.
33    pub use_scaled_drawing: bool,
34}
35
36/// The pixels of a surface in memory: premultiplied RGBA without padding.
37struct SurfacePixels {
38    /// `None` once the surface is disposed. Shared with the image of the
39    /// pixels while one is held: the pixels are copied only when they are
40    /// drawn into while an image of them is still in use.
41    data: Option<std::sync::Arc<Vec<u8>>>,
42    /// Whether anything was drawn: a surface nothing was drawn to has no
43    /// backdrop for the next scene.
44    has_content: bool,
45    /// The pixels as an image, until they change.
46    image: Option<ImageData>,
47}
48
49/// Vello render target that writes to a surface in memory: a layer.
50///
51/// A scene is rendered into the pixels of the surface in whatever rendering
52/// mode draws it; a layer that stays on the GPU is part of the GPU modes
53/// (design document, stages 7 and 8).
54pub struct SurfaceRenderTarget {
55    use_scaled_drawing: bool,
56    pixels: Rc<RefCell<SurfacePixels>>,
57    rendering_modes: Vec<VelloRenderingMode>,
58    dpi: Vector,
59    pixel_size: PixelSize,
60    version: Rc<Cell<i32>>,
61}
62
63impl SurfaceRenderTarget {
64    /// Creates a surface render target. A surface is at least one pixel in
65    /// each direction.
66    ///
67    /// # Panics
68    /// Panics when the surface is larger than a scene can be.
69    pub fn new(create_info: SurfaceRenderTargetCreateInfo) -> Self {
70        let pixel_size = PixelSize::new(create_info.width.max(1), create_info.height.max(1));
71        let (width, height) = scene_size(pixel_size);
72        crate::perf::count(crate::perf::Phase::SurfaceInMemory, u64::from(width) * u64::from(height) * 4);
73
74        Self {
75            use_scaled_drawing: create_info.use_scaled_drawing,
76            pixels: Rc::new(RefCell::new(SurfacePixels {
77                data: Some(std::sync::Arc::new(vec![0u8; width as usize * height as usize * 4])),
78                has_content: false,
79                image: None,
80            })),
81            rendering_modes: create_info.rendering_modes,
82            dpi: create_info.dpi,
83            pixel_size,
84            version: Rc::new(Cell::new(1)),
85        }
86    }
87
88    /// The properties of this render target.
89    pub fn properties(&self) -> RenderTargetProperties {
90        RenderTargetProperties::default()
91    }
92
93    /// Whether the render target lives on a GPU context and can only be used
94    /// with it. Never the case for pixels in memory.
95    pub fn has_render_context_affinity(&self) -> bool {
96        false
97    }
98
99    fn image_of(pixels: &mut SurfacePixels, pixel_size: PixelSize) -> Option<ImageData> {
100        if pixels.image.is_none() {
101            pixels.image = Some(to_shared_image(pixels.data.as_ref()?.clone(), pixel_size));
102        }
103        pixels.image.clone()
104    }
105}
106
107impl IBitmapImpl for SurfaceRenderTarget {
108    fn dpi(&self) -> Vector {
109        self.dpi
110    }
111
112    fn pixel_size(&self) -> PixelSize {
113        self.pixel_size
114    }
115
116    fn version(&self) -> i32 {
117        self.version.get()
118    }
119
120    fn save(&self, stream: &mut dyn Write, options: &BitmapEncoderOptions) -> io::Result<()> {
121        let image = self.image().ok_or_else(|| io::Error::other("SurfaceRenderTarget has been disposed"))?;
122        image_saving_helper::save_image(&image, stream, options)
123    }
124
125    fn dispose(&self) {
126        let mut pixels = self.pixels.borrow_mut();
127        pixels.data = None;
128        pixels.image = None;
129    }
130
131    fn as_any(&self) -> &dyn Any {
132        self
133    }
134}
135
136impl IDrawingContextLayerImpl for SurfaceRenderTarget {
137    fn blit(&self, context: &mut dyn IDrawingContextImpl) {
138        let context = context
139            .as_any_mut()
140            .downcast_mut::<DrawingContextImpl>()
141            .unwrap_or_else(|| panic!("The layer can only be blitted onto a drawing context of the Vello backend"));
142
143        if let Some(image) = self.image() {
144            context.draw_image_in_device_space(image, BlendMode::default());
145        }
146    }
147
148    fn can_blit(&self) -> bool {
149        true
150    }
151
152    fn is_corrupted(&self) -> bool {
153        false
154    }
155
156    fn create_shared_snapshot(&self) -> std::sync::Arc<ferroui_base::platform::SharedBitmapImpl> {
157        let image = self.image().unwrap_or_else(|| panic!("SurfaceRenderTarget has been disposed"));
158        std::sync::Arc::new(ImmutableBitmap::from_image(image))
159    }
160
161    fn create_drawing_context(&self) -> Box<dyn IDrawingContextImpl> {
162        Box::new(self.create_drawing_context_with(Vec::new()))
163    }
164}
165
166impl SurfaceRenderTarget {
167    /// Whether a scene was rendered into the surface.
168    pub fn has_content(&self) -> bool {
169        self.pixels.borrow().has_content
170    }
171
172    /// Creates a drawing context that draws into the surface. `disposables`
173    /// run in order when the context is disposed, after the scene was
174    /// rendered into the pixels.
175    pub fn create_drawing_context_with(&self, disposables: Vec<Box<dyn FnOnce()>>) -> DrawingContextImpl {
176        let (width, height) = scene_size(self.pixel_size);
177        let sink = create_scene_sink(&self.rendering_modes, width, height);
178
179        // What the surface holds stays under the scene. The CPU sink
180        // composes over the pixels it is given; a sink of a device renders
181        // into a texture of its own and is given the pixels as an image.
182        let backdrop = self.pixels.borrow().has_content.then(|| {
183            let (pixels, pixel_size) = (self.pixels.clone(), self.pixel_size);
184            VelloBackdrop {
185                composes_over_target: sink.rendering_mode() == VelloRenderingMode::Cpu,
186                brush: Box::new(move || {
187                    Self::image_of(&mut pixels.borrow_mut(), pixel_size).map(|image| {
188                        VelloSceneBrush::Image(VelloSceneImage {
189                            image,
190                            x_extend: Extend::Pad,
191                            y_extend: Extend::Pad,
192                            quality: ImageQuality::Low,
193                            alpha: 1.0,
194                        })
195                    })
196                }),
197            }
198        });
199
200        let pixels = self.pixels.clone();
201        let create_info = CreateInfo {
202            sink,
203            backdrop,
204            on_finished: Box::new(move |sink| {
205                let mut pixels = pixels.borrow_mut();
206                pixels.image = None;
207                pixels.has_content = true;
208                if let Some(data) = pixels.data.as_mut() {
209                    // The pixels are this surface's alone again unless an
210                    // image of them is still drawn somewhere (the scene
211                    // that is rendered may paint with it): then they are
212                    // copied here.
213                    if std::sync::Arc::strong_count(data) > 1 {
214                        crate::perf::count(crate::perf::Phase::PixelCopy, data.len() as u64);
215                    }
216                    sink.render_to_pixels(&mut std::sync::Arc::make_mut(data)[..]);
217                }
218            }),
219            scale_drawing_to_dpi: self.use_scaled_drawing,
220            dpi: self.dpi,
221            rendering_modes: self.rendering_modes.clone(),
222        };
223
224        let version = self.version.clone();
225        let mut all: Vec<Box<dyn FnOnce()>> = vec![Box::new(move || version.set(version.get() + 1))];
226        all.extend(disposables);
227        DrawingContextImpl::new(create_info, all)
228    }
229}
230
231impl IDrawableBitmapImpl for SurfaceRenderTarget {
232    fn image(&self) -> Option<ImageData> {
233        Self::image_of(&mut self.pixels.borrow_mut(), self.pixel_size)
234    }
235}