| 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 | |
| 6 | use core::num::NonZeroU32; |
| 7 | use core::ops::DerefMut; |
| 8 | use i_slint_core::platform::PlatformError; |
| 9 | pub use i_slint_core::software_renderer::SoftwareRenderer; |
| 10 | use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel}; |
| 11 | use i_slint_core::{graphics::RequestedGraphicsAPI, graphics::Rgb8Pixel}; |
| 12 | use std::{cell::RefCell, rc::Rc}; |
| 13 | |
| 14 | use super::WinitCompatibleRenderer; |
| 15 | |
| 16 | pub 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)] |
| 25 | struct SoftBufferPixel(pub u32); |
| 26 | |
| 27 | impl 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 | |
| 40 | impl 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 | |
| 52 | impl 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 | |
| 68 | impl 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 | |
| 78 | impl 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 | |