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