slint/internal/backends/winit/renderer/sw.rs
Simon Hausmann 1c73144b09 Winit: Implement the "resume protocol"
In future versions of winit, it'll only be possible to create windows
when the active event loop signals availability. To upgrade to the latest
accesskit version as well as to run on iOS, we're going to implement support
for this earlier and stop using the deprecrated create_window() function
on the regular (not running) EventLoop.

Co-Authored-By: Olivier Goffart <olivier.goffart@slint.dev>
2025-05-09 09:36:37 +02:00

211 lines
7.4 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
//! Delegate the rendering to the [`i_slint_core::software_renderer::SoftwareRenderer`]
use core::num::NonZeroU32;
use core::ops::DerefMut;
use i_slint_core::platform::PlatformError;
pub use i_slint_core::software_renderer::SoftwareRenderer;
use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel};
use i_slint_core::{graphics::RequestedGraphicsAPI, graphics::Rgb8Pixel};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use winit::event_loop::ActiveEventLoop;
use super::WinitCompatibleRenderer;
pub struct WinitSoftwareRenderer {
renderer: SoftwareRenderer,
_context: RefCell<Option<softbuffer::Context<Arc<winit::window::Window>>>>,
surface: RefCell<
Option<softbuffer::Surface<Arc<winit::window::Window>, Arc<winit::window::Window>>>,
>,
}
#[repr(transparent)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct SoftBufferPixel(pub u32);
impl From<SoftBufferPixel> for PremultipliedRgbaColor {
#[inline]
fn from(pixel: SoftBufferPixel) -> Self {
let v = pixel.0;
PremultipliedRgbaColor {
red: (v >> 16) as u8,
green: (v >> 8) as u8,
blue: (v >> 0) as u8,
alpha: (v >> 24) as u8,
}
}
}
impl From<PremultipliedRgbaColor> for SoftBufferPixel {
#[inline]
fn from(pixel: PremultipliedRgbaColor) -> Self {
Self(
(pixel.alpha as u32) << 24
| ((pixel.red as u32) << 16)
| ((pixel.green as u32) << 8)
| (pixel.blue as u32),
)
}
}
impl TargetPixel for SoftBufferPixel {
fn blend(&mut self, color: PremultipliedRgbaColor) {
let mut x = PremultipliedRgbaColor::from(*self);
x.blend(color);
*self = x.into();
}
fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self(0xff000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
}
fn background() -> Self {
Self(0)
}
}
impl WinitSoftwareRenderer {
pub fn new_suspended(
_shared_backend_data: &Rc<crate::SharedBackendData>,
) -> Box<dyn WinitCompatibleRenderer> {
Box::new(Self {
renderer: SoftwareRenderer::new(),
_context: RefCell::new(None),
surface: RefCell::new(None),
})
}
}
impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError> {
let size = window.size();
let Some((width, height)) = size.width.try_into().ok().zip(size.height.try_into().ok())
else {
// Nothing to render
return Ok(());
};
let mut borrowed_surface = self.surface.borrow_mut();
let Some(surface) = borrowed_surface.as_mut() else {
// Nothing to render
return Ok(());
};
let winit_window = surface.window().clone();
surface
.resize(width, height)
.map_err(|e| format!("Error resizing softbuffer surface: {e}"))?;
let mut target_buffer = surface
.buffer_mut()
.map_err(|e| format!("Error retrieving softbuffer rendering buffer: {e}"))?;
let age = target_buffer.age();
self.renderer.set_repaint_buffer_type(match age {
1 => RepaintBufferType::ReusedBuffer,
2 => RepaintBufferType::SwappedBuffers,
_ => RepaintBufferType::NewBuffer,
});
let region = if std::env::var_os("SLINT_LINE_BY_LINE").is_none() {
let buffer: &mut [SoftBufferPixel] =
bytemuck::cast_slice_mut(target_buffer.deref_mut());
self.renderer.render(buffer, width.get() as usize)
} else {
// SLINT_LINE_BY_LINE is set and this is a debug mode where we also render in a Rgb565Pixel
struct FrameBuffer<'a> {
buffer: &'a mut [u32],
line: Vec<i_slint_core::software_renderer::Rgb565Pixel>,
}
impl i_slint_core::software_renderer::LineBufferProvider for FrameBuffer<'_> {
type TargetPixel = i_slint_core::software_renderer::Rgb565Pixel;
fn process_line(
&mut self,
line: usize,
range: core::ops::Range<usize>,
render_fn: impl FnOnce(&mut [Self::TargetPixel]),
) {
let line_begin = line * self.line.len();
let sub = &mut self.line[..range.len()];
render_fn(sub);
for (dst, src) in self.buffer[line_begin..][range].iter_mut().zip(sub) {
let p = Rgb8Pixel::from(*src);
*dst =
0xff000000 | ((p.r as u32) << 16) | ((p.g as u32) << 8) | (p.b as u32);
}
}
}
self.renderer.render_by_line(FrameBuffer {
buffer: &mut target_buffer,
line: vec![Default::default(); width.get() as usize],
})
};
winit_window.pre_present_notify();
let size = region.bounding_box_size();
if let Some((w, h)) = Option::zip(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
let pos = region.bounding_box_origin();
target_buffer
.present_with_damage(&[softbuffer::Rect {
width: w,
height: h,
x: pos.x as u32,
y: pos.y as u32,
}])
.map_err(|e| format!("Error presenting softbuffer buffer: {e}"))?;
}
Ok(())
}
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
&self.renderer
}
fn occluded(&self, _: bool) {
// On X11, the buffer is completely cleared when the window is hidden
// and the buffer age doesn't respect that, so clean the partial rendering cache
self.renderer.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
}
fn resume(
&self,
active_event_loop: &ActiveEventLoop,
window_attributes: winit::window::WindowAttributes,
_requested_graphics_api: Option<RequestedGraphicsAPI>,
) -> Result<Arc<winit::window::Window>, PlatformError> {
let winit_window =
active_event_loop.create_window(window_attributes).map_err(|winit_os_error| {
PlatformError::from(format!(
"Error creating native window for software rendering: {winit_os_error}"
))
})?;
let winit_window = Arc::new(winit_window);
let context = softbuffer::Context::new(winit_window.clone())
.map_err(|e| format!("Error creating softbuffer context: {e}"))?;
let surface = softbuffer::Surface::new(&context, winit_window.clone()).map_err(
|softbuffer_error| format!("Error creating softbuffer surface: {softbuffer_error}"),
)?;
*self._context.borrow_mut() = Some(context);
*self.surface.borrow_mut() = Some(surface);
Ok(winit_window)
}
fn suspend(&self) -> Result<(), PlatformError> {
drop(self.surface.borrow_mut().take());
drop(self._context.borrow_mut().take());
Ok(())
}
}