iced_tiny_skia/window/
compositor.rs

1use crate::core::{Color, Rectangle, Size};
2use crate::graphics::compositor::{self, Information};
3use crate::graphics::damage;
4use crate::graphics::error::{self, Error};
5use crate::graphics::{self, Viewport};
6use crate::{Layer, Renderer, Settings};
7
8use std::collections::VecDeque;
9use std::num::NonZeroU32;
10
11#[allow(missing_debug_implementations)]
12pub struct Compositor {
13    context: softbuffer::Context<Box<dyn compositor::Window>>,
14    settings: Settings,
15}
16
17#[allow(missing_debug_implementations)]
18pub struct Surface {
19    window: softbuffer::Surface<
20        Box<dyn compositor::Window>,
21        Box<dyn compositor::Window>,
22    >,
23    clip_mask: tiny_skia::Mask,
24    layer_stack: VecDeque<Vec<Layer>>,
25    background_color: Color,
26    max_age: u8,
27}
28
29impl crate::graphics::Compositor for Compositor {
30    type Renderer = Renderer;
31    type Surface = Surface;
32
33    async fn with_backend<W: compositor::Window>(
34        settings: graphics::Settings,
35        compatible_window: W,
36        backend: Option<&str>,
37    ) -> Result<Self, Error> {
38        match backend {
39            None | Some("tiny-skia") | Some("tiny_skia") => {
40                Ok(new(settings.into(), compatible_window))
41            }
42            Some(backend) => Err(Error::GraphicsAdapterNotFound {
43                backend: "tiny-skia",
44                reason: error::Reason::DidNotMatch {
45                    preferred_backend: backend.to_owned(),
46                },
47            }),
48        }
49    }
50
51    fn create_renderer(&self) -> Self::Renderer {
52        Renderer::new(
53            self.settings.default_font,
54            self.settings.default_text_size,
55        )
56    }
57
58    fn create_surface<W: compositor::Window + Clone>(
59        &mut self,
60        window: W,
61        width: u32,
62        height: u32,
63    ) -> Self::Surface {
64        let window = softbuffer::Surface::new(
65            &self.context,
66            Box::new(window.clone()) as _,
67        )
68        .expect("Create softbuffer surface for window");
69
70        let mut surface = Surface {
71            window,
72            clip_mask: tiny_skia::Mask::new(width, height)
73                .expect("Create clip mask"),
74            layer_stack: VecDeque::new(),
75            background_color: Color::TRANSPARENT,
76            max_age: 0,
77        };
78
79        self.configure_surface(&mut surface, width, height);
80
81        surface
82    }
83
84    fn configure_surface(
85        &mut self,
86        surface: &mut Self::Surface,
87        width: u32,
88        height: u32,
89    ) {
90        surface
91            .window
92            .resize(
93                NonZeroU32::new(width).expect("Non-zero width"),
94                NonZeroU32::new(height).expect("Non-zero height"),
95            )
96            .expect("Resize surface");
97
98        surface.clip_mask =
99            tiny_skia::Mask::new(width, height).expect("Create clip mask");
100        surface.layer_stack.clear();
101    }
102
103    fn fetch_information(&self) -> Information {
104        Information {
105            adapter: String::from("CPU"),
106            backend: String::from("tiny-skia"),
107        }
108    }
109
110    fn present<T: AsRef<str>>(
111        &mut self,
112        renderer: &mut Self::Renderer,
113        surface: &mut Self::Surface,
114        viewport: &Viewport,
115        background_color: Color,
116        overlay: &[T],
117    ) -> Result<(), compositor::SurfaceError> {
118        present(renderer, surface, viewport, background_color, overlay)
119    }
120
121    fn screenshot<T: AsRef<str>>(
122        &mut self,
123        renderer: &mut Self::Renderer,
124        viewport: &Viewport,
125        background_color: Color,
126        overlay: &[T],
127    ) -> Vec<u8> {
128        screenshot(renderer, viewport, background_color, overlay)
129    }
130}
131
132pub fn new<W: compositor::Window>(
133    settings: Settings,
134    compatible_window: W,
135) -> Compositor {
136    #[allow(unsafe_code)]
137    let context = softbuffer::Context::new(Box::new(compatible_window) as _)
138        .expect("Create softbuffer context");
139
140    Compositor { context, settings }
141}
142
143pub fn present<T: AsRef<str>>(
144    renderer: &mut Renderer,
145    surface: &mut Surface,
146    viewport: &Viewport,
147    background_color: Color,
148    overlay: &[T],
149) -> Result<(), compositor::SurfaceError> {
150    let physical_size = viewport.physical_size();
151
152    let mut buffer = surface
153        .window
154        .buffer_mut()
155        .map_err(|_| compositor::SurfaceError::Lost)?;
156
157    let last_layers = {
158        let age = buffer.age();
159
160        surface.max_age = surface.max_age.max(age);
161        surface.layer_stack.truncate(surface.max_age as usize);
162
163        if age > 0 {
164            surface.layer_stack.get(age as usize - 1)
165        } else {
166            None
167        }
168    };
169
170    let damage = last_layers
171        .and_then(|last_layers| {
172            (surface.background_color == background_color).then(|| {
173                damage::diff(
174                    last_layers,
175                    renderer.layers(),
176                    |layer| vec![layer.bounds],
177                    Layer::damage,
178                )
179            })
180        })
181        .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
182
183    // TODO(POP): I tried to adapt this to what I saw in the diff, which was essentially making sure this is called
184    // before the damage.is_empty() check
185    surface.layer_stack.push_front(renderer.layers().to_vec());
186
187    // TODO better handling of no damage. As it is, the winit shell does not handle a skipped present well.
188    // if damage.is_empty() {
189    //     return Err(compositor::SurfaceError::NoDamage);
190    // }
191
192    surface.background_color = background_color;
193
194    let damage =
195        damage::group(damage, Rectangle::with_size(viewport.logical_size()));
196
197    let mut pixels = tiny_skia::PixmapMut::from_bytes(
198        bytemuck::cast_slice_mut(&mut buffer),
199        physical_size.width,
200        physical_size.height,
201    )
202    .expect("Create pixel map");
203
204    // let damage = damage::group(damage, scale_factor, physical_size);
205    // TODO(POP): Is this something that needs to be adapted?
206
207    renderer.draw(
208        &mut pixels,
209        &mut surface.clip_mask,
210        viewport,
211        &damage,
212        background_color,
213        overlay,
214    );
215
216    buffer.present().map_err(|_| compositor::SurfaceError::Lost)
217}
218
219pub fn screenshot<T: AsRef<str>>(
220    renderer: &mut Renderer,
221    viewport: &Viewport,
222    background_color: Color,
223    overlay: &[T],
224) -> Vec<u8> {
225    let size = viewport.physical_size();
226
227    let mut offscreen_buffer: Vec<u32> =
228        vec![0; size.width as usize * size.height as usize];
229
230    let mut clip_mask = tiny_skia::Mask::new(size.width, size.height)
231        .expect("Create clip mask");
232
233    renderer.draw(
234        &mut tiny_skia::PixmapMut::from_bytes(
235            bytemuck::cast_slice_mut(&mut offscreen_buffer),
236            size.width,
237            size.height,
238        )
239        .expect("Create offscreen pixel map"),
240        &mut clip_mask,
241        viewport,
242        &[Rectangle::with_size(Size::new(
243            size.width as f32,
244            size.height as f32,
245        ))],
246        background_color,
247        overlay,
248    );
249
250    offscreen_buffer.iter().fold(
251        Vec::with_capacity(offscreen_buffer.len() * 4),
252        |mut acc, pixel| {
253            const A_MASK: u32 = 0xFF_00_00_00;
254            const R_MASK: u32 = 0x00_FF_00_00;
255            const G_MASK: u32 = 0x00_00_FF_00;
256            const B_MASK: u32 = 0x00_00_00_FF;
257
258            let a = ((A_MASK & pixel) >> 24) as u8;
259            let r = ((R_MASK & pixel) >> 16) as u8;
260            let g = ((G_MASK & pixel) >> 8) as u8;
261            let b = (B_MASK & pixel) as u8;
262
263            acc.extend([r, g, b, a]);
264            acc
265        },
266    )
267}