i_slint_backend_winit/renderer/
sw.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Delegate the rendering to the [`i_slint_core::software_renderer::SoftwareRenderer`]
5
6use core::num::NonZeroU32;
7use core::ops::DerefMut;
8use i_slint_core::graphics::Rgb8Pixel;
9use i_slint_core::platform::PlatformError;
10pub use i_slint_core::software_renderer::SoftwareRenderer;
11use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel};
12use std::cell::RefCell;
13use std::rc::Rc;
14use std::sync::Arc;
15use winit::event_loop::ActiveEventLoop;
16
17use super::WinitCompatibleRenderer;
18
19pub struct WinitSoftwareRenderer {
20    renderer: SoftwareRenderer,
21    _context: RefCell<Option<softbuffer::Context<Arc<winit::window::Window>>>>,
22    surface: RefCell<
23        Option<softbuffer::Surface<Arc<winit::window::Window>, Arc<winit::window::Window>>>,
24    >,
25}
26
27#[repr(transparent)]
28#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
29struct SoftBufferPixel(pub u32);
30
31impl From<SoftBufferPixel> for PremultipliedRgbaColor {
32    #[inline]
33    fn from(pixel: SoftBufferPixel) -> Self {
34        let v = pixel.0;
35        PremultipliedRgbaColor {
36            red: (v >> 16) as u8,
37            green: (v >> 8) as u8,
38            blue: (v >> 0) as u8,
39            alpha: (v >> 24) as u8,
40        }
41    }
42}
43
44impl From<PremultipliedRgbaColor> for SoftBufferPixel {
45    #[inline]
46    fn from(pixel: PremultipliedRgbaColor) -> Self {
47        Self(
48            (pixel.alpha as u32) << 24
49                | ((pixel.red as u32) << 16)
50                | ((pixel.green as u32) << 8)
51                | (pixel.blue as u32),
52        )
53    }
54}
55
56impl TargetPixel for SoftBufferPixel {
57    fn blend(&mut self, color: PremultipliedRgbaColor) {
58        let mut x = PremultipliedRgbaColor::from(*self);
59        x.blend(color);
60        *self = x.into();
61    }
62
63    fn from_rgb(r: u8, g: u8, b: u8) -> Self {
64        Self(0xff000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
65    }
66
67    fn background() -> Self {
68        Self(0)
69    }
70}
71
72impl WinitSoftwareRenderer {
73    pub fn new_suspended(
74        _shared_backend_data: &Rc<crate::SharedBackendData>,
75    ) -> Result<Box<dyn WinitCompatibleRenderer>, PlatformError> {
76        Ok(Box::new(Self {
77            renderer: SoftwareRenderer::new(),
78            _context: RefCell::new(None),
79            surface: RefCell::new(None),
80        }))
81    }
82}
83
84impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
85    fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError> {
86        let size = window.size();
87
88        let Some((width, height)) = size.width.try_into().ok().zip(size.height.try_into().ok())
89        else {
90            // Nothing to render
91            return Ok(());
92        };
93
94        let mut borrowed_surface = self.surface.borrow_mut();
95        let Some(surface) = borrowed_surface.as_mut() else {
96            // Nothing to render
97            return Ok(());
98        };
99
100        let winit_window = surface.window().clone();
101
102        surface
103            .resize(width, height)
104            .map_err(|e| format!("Error resizing softbuffer surface: {e}"))?;
105
106        let mut target_buffer = surface
107            .buffer_mut()
108            .map_err(|e| format!("Error retrieving softbuffer rendering buffer: {e}"))?;
109
110        let age = target_buffer.age();
111        self.renderer.set_repaint_buffer_type(match age {
112            1 => RepaintBufferType::ReusedBuffer,
113            2 => RepaintBufferType::SwappedBuffers,
114            _ => RepaintBufferType::NewBuffer,
115        });
116
117        let region = if std::env::var_os("SLINT_LINE_BY_LINE").is_none() {
118            let buffer: &mut [SoftBufferPixel] =
119                bytemuck::cast_slice_mut(target_buffer.deref_mut());
120            self.renderer.render(buffer, width.get() as usize)
121        } else {
122            // SLINT_LINE_BY_LINE is set and this is a debug mode where we also render in a Rgb565Pixel
123            struct FrameBuffer<'a> {
124                buffer: &'a mut [u32],
125                line: Vec<i_slint_core::software_renderer::Rgb565Pixel>,
126            }
127            impl i_slint_core::software_renderer::LineBufferProvider for FrameBuffer<'_> {
128                type TargetPixel = i_slint_core::software_renderer::Rgb565Pixel;
129                fn process_line(
130                    &mut self,
131                    line: usize,
132                    range: core::ops::Range<usize>,
133                    render_fn: impl FnOnce(&mut [Self::TargetPixel]),
134                ) {
135                    let line_begin = line * self.line.len();
136                    let sub = &mut self.line[..range.len()];
137                    render_fn(sub);
138                    for (dst, src) in self.buffer[line_begin..][range].iter_mut().zip(sub) {
139                        let p = Rgb8Pixel::from(*src);
140                        *dst =
141                            0xff000000 | ((p.r as u32) << 16) | ((p.g as u32) << 8) | (p.b as u32);
142                    }
143                }
144            }
145            self.renderer.render_by_line(FrameBuffer {
146                buffer: &mut target_buffer,
147                line: vec![Default::default(); width.get() as usize],
148            })
149        };
150
151        winit_window.pre_present_notify();
152
153        let size = region.bounding_box_size();
154        if let Some((w, h)) = Option::zip(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
155        {
156            let pos = region.bounding_box_origin();
157            target_buffer
158                .present_with_damage(&[softbuffer::Rect {
159                    width: w,
160                    height: h,
161                    x: pos.x as u32,
162                    y: pos.y as u32,
163                }])
164                .map_err(|e| format!("Error presenting softbuffer buffer: {e}"))?;
165        }
166        Ok(())
167    }
168
169    fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
170        &self.renderer
171    }
172
173    fn occluded(&self, _: bool) {
174        // On X11, the buffer is completely cleared when the window is hidden
175        // and the buffer age doesn't respect that, so clean the partial rendering cache
176        self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
177    }
178
179    fn resume(
180        &self,
181        active_event_loop: &ActiveEventLoop,
182        window_attributes: winit::window::WindowAttributes,
183    ) -> Result<Arc<winit::window::Window>, PlatformError> {
184        let winit_window =
185            active_event_loop.create_window(window_attributes).map_err(|winit_os_error| {
186                PlatformError::from(format!(
187                    "Error creating native window for software rendering: {winit_os_error}"
188                ))
189            })?;
190        let winit_window = Arc::new(winit_window);
191
192        let context = softbuffer::Context::new(winit_window.clone())
193            .map_err(|e| format!("Error creating softbuffer context: {e}"))?;
194
195        let surface = softbuffer::Surface::new(&context, winit_window.clone()).map_err(
196            |softbuffer_error| format!("Error creating softbuffer surface: {softbuffer_error}"),
197        )?;
198
199        *self._context.borrow_mut() = Some(context);
200        *self.surface.borrow_mut() = Some(surface);
201
202        Ok(winit_window)
203    }
204
205    fn suspend(&self) -> Result<(), PlatformError> {
206        drop(self.surface.borrow_mut().take());
207        drop(self._context.borrow_mut().take());
208        Ok(())
209    }
210}