Replace the MAX_BUFFER_AGE const generic with a runtime enum

Having a const generic for that didn't turn to be a good API.
Also made the C++ side more difficult

(Also renamed buffer_stride to pixel_stride)

Closes #2135
This commit is contained in:
Olivier Goffart 2023-02-08 14:44:01 +01:00 committed by GitHub
parent 05e00fe057
commit a19efc30db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 134 additions and 148 deletions

View file

@ -17,6 +17,8 @@ All notable changes to this project are documented in this file.
a `slint::SharedString`. a `slint::SharedString`.
- `slint::platform::WindowEvent` does not derive from `Copy` anymore. You must `clone()` it - `slint::platform::WindowEvent` does not derive from `Copy` anymore. You must `clone()` it
explicitly if you want to create a copy. explicitly if you want to create a copy.
- In Rust, the MAX_BUFFER_AGE const parameter of `slint::platform::software_renderer::MinimalSoftwareWindow`
has been removed and replaced by an argument to the `new()` function
### Added ### Added

View file

@ -194,12 +194,6 @@ public:
/// To be used as a template parameter of the WindowAdapter. /// To be used as a template parameter of the WindowAdapter.
/// ///
/// Use the render() function to render in a buffer /// Use the render() function to render in a buffer
///
/// The MAX_BUFFER_AGE parameter specifies how many buffers are being re-used.
/// This means that the buffer passed to the render functions still contains a rendering of
/// the window that was refreshed as least that amount of frame ago.
/// It will impact how much of the screen needs to be redrawn.
template<int MAX_BUFFER_AGE = 0>
class SoftwareRenderer class SoftwareRenderer
{ {
mutable cbindgen_private::SoftwareRendererOpaque inner; mutable cbindgen_private::SoftwareRendererOpaque inner;
@ -208,7 +202,7 @@ public:
virtual ~SoftwareRenderer() virtual ~SoftwareRenderer()
{ {
if (inner) { if (inner) {
cbindgen_private::slint_software_renderer_drop(MAX_BUFFER_AGE, inner); cbindgen_private::slint_software_renderer_drop(inner);
} }
}; };
SoftwareRenderer(const SoftwareRenderer &) = delete; SoftwareRenderer(const SoftwareRenderer &) = delete;
@ -216,18 +210,18 @@ public:
SoftwareRenderer() = default; SoftwareRenderer() = default;
/// \private /// \private
void init(const cbindgen_private::WindowAdapterRcOpaque *win) const void init(const cbindgen_private::WindowAdapterRcOpaque *win, int max_buffer_age) const
{ {
if (inner) { if (inner) {
cbindgen_private::slint_software_renderer_drop(MAX_BUFFER_AGE, inner); cbindgen_private::slint_software_renderer_drop(inner);
} }
inner = cbindgen_private::slint_software_renderer_new(MAX_BUFFER_AGE, win); inner = cbindgen_private::slint_software_renderer_new(max_buffer_age, win);
} }
/// \private /// \private
cbindgen_private::RendererPtr renderer_handle() const cbindgen_private::RendererPtr renderer_handle() const
{ {
return cbindgen_private::slint_software_renderer_handle(MAX_BUFFER_AGE, inner); return cbindgen_private::slint_software_renderer_handle(inner);
} }
/// Render the window scene into a pixel buffer /// Render the window scene into a pixel buffer
@ -236,10 +230,11 @@ public:
/// ///
/// The stride is the amount of pixels between two lines in the buffer. /// The stride is the amount of pixels between two lines in the buffer.
/// It is must be at least as large as the width of the window. /// It is must be at least as large as the width of the window.
void render(std::span<slint::cbindgen_private::Rgb8Pixel> buffer, std::size_t stride) const void render(std::span<slint::cbindgen_private::Rgb8Pixel> buffer,
std::size_t pixel_stride) const
{ {
cbindgen_private::slint_software_renderer_render_rgb8(MAX_BUFFER_AGE, inner, buffer.data(), cbindgen_private::slint_software_renderer_render_rgb8(inner, buffer.data(), buffer.size(),
buffer.size(), stride); pixel_stride);
} }
}; };

View file

@ -6,7 +6,7 @@ use i_slint_core::api::{PhysicalSize, Window};
use i_slint_core::graphics::{IntSize, Rgb8Pixel}; use i_slint_core::graphics::{IntSize, Rgb8Pixel};
use i_slint_core::platform::Platform; use i_slint_core::platform::Platform;
use i_slint_core::renderer::Renderer; use i_slint_core::renderer::Renderer;
use i_slint_core::software_renderer::SoftwareRenderer; use i_slint_core::software_renderer::{RepaintBufferType, SoftwareRenderer};
use i_slint_core::window::ffi::WindowAdapterRcOpaque; use i_slint_core::window::ffi::WindowAdapterRcOpaque;
use i_slint_core::window::{WindowAdapter, WindowAdapterSealed}; use i_slint_core::window::{WindowAdapter, WindowAdapterSealed};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
@ -143,52 +143,35 @@ pub unsafe extern "C" fn slint_software_renderer_new(
) -> SoftwareRendererOpaque { ) -> SoftwareRendererOpaque {
let window = core::mem::transmute::<&WindowAdapterRcOpaque, &Rc<dyn WindowAdapter>>(window); let window = core::mem::transmute::<&WindowAdapterRcOpaque, &Rc<dyn WindowAdapter>>(window);
let weak = Rc::downgrade(window); let weak = Rc::downgrade(window);
match buffer_age { let repaint_buffer_type = match buffer_age {
0 => Box::into_raw(Box::new(SoftwareRenderer::<0>::new(weak))) as SoftwareRendererOpaque, 0 => RepaintBufferType::NewBuffer,
1 => Box::into_raw(Box::new(SoftwareRenderer::<1>::new(weak))) as SoftwareRendererOpaque, 1 => RepaintBufferType::ReusedBuffer,
2 => Box::into_raw(Box::new(SoftwareRenderer::<2>::new(weak))) as SoftwareRendererOpaque, 2 => RepaintBufferType::SwappedBuffers,
_ => unreachable!(), _ => unreachable!(),
} };
Box::into_raw(Box::new(SoftwareRenderer::new(repaint_buffer_type, weak)))
as SoftwareRendererOpaque
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn slint_software_renderer_drop(buffer_age: u32, r: SoftwareRendererOpaque) { pub unsafe extern "C" fn slint_software_renderer_drop(r: SoftwareRendererOpaque) {
match buffer_age { drop(Box::from_raw(r as *mut SoftwareRenderer));
0 => drop(Box::from_raw(r as *mut SoftwareRenderer<0>)),
1 => drop(Box::from_raw(r as *mut SoftwareRenderer<1>)),
2 => drop(Box::from_raw(r as *mut SoftwareRenderer<2>)),
_ => unreachable!(),
}
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn slint_software_renderer_render_rgb8( pub unsafe extern "C" fn slint_software_renderer_render_rgb8(
buffer_age: u32,
r: SoftwareRendererOpaque, r: SoftwareRendererOpaque,
buffer: *mut Rgb8Pixel, buffer: *mut Rgb8Pixel,
buffer_len: usize, buffer_len: usize,
buffer_stride: usize, pixel_stride: usize,
) { ) {
let buffer = core::slice::from_raw_parts_mut(buffer, buffer_len); let buffer = core::slice::from_raw_parts_mut(buffer, buffer_len);
match buffer_age { (*(r as *const SoftwareRenderer)).render(buffer, pixel_stride)
0 => (*(r as *const SoftwareRenderer<0>)).render(buffer, buffer_stride),
1 => (*(r as *const SoftwareRenderer<1>)).render(buffer, buffer_stride),
2 => (*(r as *const SoftwareRenderer<2>)).render(buffer, buffer_stride),
_ => unreachable!(),
}
} }
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn slint_software_renderer_handle( pub unsafe extern "C" fn slint_software_renderer_handle(r: SoftwareRendererOpaque) -> RendererPtr {
buffer_age: u32, let r = (r as *const SoftwareRenderer) as *const dyn Renderer;
r: SoftwareRendererOpaque,
) -> RendererPtr {
let r = match buffer_age {
0 => (r as *const SoftwareRenderer<0>) as *const dyn Renderer,
1 => (r as *const SoftwareRenderer<1>) as *const dyn Renderer,
2 => (r as *const SoftwareRenderer<2>) as *const dyn Renderer,
_ => unreachable!(),
};
core::mem::transmute(r) core::mem::transmute(r)
} }

View file

@ -131,7 +131,7 @@ slint::include_modules!();
# */ # */
struct MyPlatform { struct MyPlatform {
window: Rc<MinimalSoftwareWindow<2>>, window: Rc<MinimalSoftwareWindow>,
// optional: some timer device from your device's HAL crate // optional: some timer device from your device's HAL crate
timer: hal::Timer, timer: hal::Timer,
// ... maybe more devices // ... maybe more devices
@ -158,7 +158,7 @@ fn main() {
// ... // ...
// Initialize a window (we'll need it later). // Initialize a window (we'll need it later).
let window = MinimalSoftwareWindow::new(); let window = MinimalSoftwareWindow::new(Default::default());
slint::platform::set_platform(Box::new(MyPlatform { slint::platform::set_platform(Box::new(MyPlatform {
window: window.clone(), window: window.clone(),
timer: hal::Timer(/*...*/), timer: hal::Timer(/*...*/),
@ -195,8 +195,8 @@ A typical super loop with Slint combines the tasks of querying input drivers, ap
rendering and possibly putting the device into a low-power sleep state. Below is an example: rendering and possibly putting the device into a low-power sleep state. Below is an example:
```rust,no_run ```rust,no_run
use slint::platform::{software_renderer::MinimalSoftwareWindow}; use slint::platform::software_renderer::MinimalSoftwareWindow;
let window = MinimalSoftwareWindow::<0>::new(); let window = MinimalSoftwareWindow::new(Default::default());
# fn check_for_touch_event() -> Option<slint::platform::WindowEvent> { todo!() } # fn check_for_touch_event() -> Option<slint::platform::WindowEvent> { todo!() }
# mod hal { pub fn wfi() {} } # mod hal { pub fn wfi() {} }
//... //...
@ -260,10 +260,11 @@ the second buffer, the back buffer.
use slint::platform::software_renderer::Rgb565Pixel; use slint::platform::software_renderer::Rgb565Pixel;
# fn is_swap_pending()->bool {false} fn swap_buffers() {} # fn is_swap_pending()->bool {false} fn swap_buffers() {}
// Note that we use `2` as the const generic parameter which is our buffer count, // In this example, we have two buffer: one is currently displayed, and we are
// since we have two buffer, we always need to refresh what changed in the two // rendering into the second one. Hence we use `RepaintBufferType::SwappedBuffers`
// previous frames let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
let window = slint::platform::software_renderer::MinimalSoftwareWindow::<2>::new(); slint::platform::software_renderer::RepaintBufferType::SwappedBuffers
);
const DISPLAY_WIDTH: usize = 320; const DISPLAY_WIDTH: usize = 320;
const DISPLAY_HEIGHT: usize = 240; const DISPLAY_HEIGHT: usize = 240;
@ -357,11 +358,13 @@ impl<T: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>
} }
} }
// Note that we use `1` as the const generic parameter for MinimalSoftwareWindow to indicate // Note that we use `ReusedBuffer` as parameter for MinimalSoftwareWindow to indicate
// the maximum age of the buffer we provide to `render_fn` inside `process_line`. // that we just need to re-render what changed since the last frame.
// What's shown on the screen buffer is not in our RAM, but actually within the display itself. // What's shown on the screen buffer is not in our RAM, but actually within the display itself.
// We just need to re-render what changed since the last frame. // Only the changed part of the screen will be updated.
let window = slint::platform::software_renderer::MinimalSoftwareWindow::<1>::new(); let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::ReusedBuffer
);
const DISPLAY_WIDTH: usize = 320; const DISPLAY_WIDTH: usize = 320;
let mut line_buffer = [slint::platform::software_renderer::Rgb565Pixel(0); DISPLAY_WIDTH]; let mut line_buffer = [slint::platform::software_renderer::Rgb565Pixel(0); DISPLAY_WIDTH];

View file

@ -36,12 +36,14 @@ pub fn init() {
#[derive(Default)] #[derive(Default)]
struct EspBackend { struct EspBackend {
window: RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow<1>>>>, window: RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow>>>,
} }
impl slint::platform::Platform for EspBackend { impl slint::platform::Platform for EspBackend {
fn create_window_adapter(&self) -> Rc<dyn slint::platform::WindowAdapter> { fn create_window_adapter(&self) -> Rc<dyn slint::platform::WindowAdapter> {
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(); let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::ReusedBuffer,
);
self.window.replace(Some(window.clone())); self.window.replace(Some(window.clone()));
window window
} }

View file

@ -33,12 +33,14 @@ pub fn init() {
#[derive(Default)] #[derive(Default)]
struct EspBackend { struct EspBackend {
window: RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow<1>>>>, window: RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow>>>,
} }
impl slint::platform::Platform for EspBackend { impl slint::platform::Platform for EspBackend {
fn create_window_adapter(&self) -> Rc<dyn slint::platform::WindowAdapter> { fn create_window_adapter(&self) -> Rc<dyn slint::platform::WindowAdapter> {
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(); let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::ReusedBuffer,
);
self.window.replace(Some(window.clone())); self.window.replace(Some(window.clone()));
window window
} }

View file

@ -58,12 +58,13 @@ pub fn init() {
#[derive(Default)] #[derive(Default)]
struct PicoBackend { struct PicoBackend {
window: RefCell<Option<Rc<renderer::MinimalSoftwareWindow<1>>>>, window: RefCell<Option<Rc<renderer::MinimalSoftwareWindow>>>,
} }
impl slint::platform::Platform for PicoBackend { impl slint::platform::Platform for PicoBackend {
fn create_window_adapter(&self) -> Rc<dyn slint::platform::WindowAdapter> { fn create_window_adapter(&self) -> Rc<dyn slint::platform::WindowAdapter> {
let window = renderer::MinimalSoftwareWindow::new(); let window =
renderer::MinimalSoftwareWindow::new(renderer::RepaintBufferType::ReusedBuffer);
self.window.replace(Some(window.clone())); self.window.replace(Some(window.clone()));
window window
} }

View file

@ -40,14 +40,15 @@ pub fn init() {
#[derive(Default)] #[derive(Default)]
struct StmBackend { struct StmBackend {
window: core::cell::RefCell< window:
Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow<2>>>, core::cell::RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow>>>,
>,
timer: once_cell::unsync::OnceCell<hal::timer::Timer<pac::TIM2>>, timer: once_cell::unsync::OnceCell<hal::timer::Timer<pac::TIM2>>,
} }
impl slint::platform::Platform for StmBackend { impl slint::platform::Platform for StmBackend {
fn create_window_adapter(&self) -> Rc<dyn slint::platform::WindowAdapter> { fn create_window_adapter(&self) -> Rc<dyn slint::platform::WindowAdapter> {
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(); let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::SwappedBuffers,
);
self.window.replace(Some(window.clone())); self.window.replace(Some(window.clone()));
window window
} }

View file

@ -73,7 +73,7 @@ cfg_if::cfg_if! {
} else if #[cfg(enable_skia_renderer)] { } else if #[cfg(enable_skia_renderer)] {
type DefaultRenderer = renderer::skia::SkiaRenderer; type DefaultRenderer = renderer::skia::SkiaRenderer;
} else if #[cfg(feature = "renderer-winit-software")] { } else if #[cfg(feature = "renderer-winit-software")] {
type DefaultRenderer = renderer::sw::WinitSoftwareRenderer<0>; type DefaultRenderer = renderer::sw::WinitSoftwareRenderer;
} else { } else {
compile_error!("Please select a feature to build with the winit backend: `renderer-winit-femtovg`, `renderer-winit-skia`, `renderer-winit-skia-opengl` or `renderer-winit-software`"); compile_error!("Please select a feature to build with the winit backend: `renderer-winit-femtovg`, `renderer-winit-skia`, `renderer-winit-skia-opengl` or `renderer-winit-software`");
} }
@ -102,7 +102,7 @@ impl Backend {
Some("skia") => window_factory_fn::<renderer::skia::SkiaRenderer>, Some("skia") => window_factory_fn::<renderer::skia::SkiaRenderer>,
#[cfg(feature = "renderer-winit-software")] #[cfg(feature = "renderer-winit-software")]
Some("sw") | Some("software") => { Some("sw") | Some("software") => {
window_factory_fn::<renderer::sw::WinitSoftwareRenderer<0>> window_factory_fn::<renderer::sw::WinitSoftwareRenderer>
} }
None => window_factory_fn::<DefaultRenderer>, None => window_factory_fn::<DefaultRenderer>,
Some(renderer_name) => { Some(renderer_name) => {

View file

@ -10,19 +10,20 @@ use i_slint_core::window::WindowAdapter;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
pub struct WinitSoftwareRenderer<const MAX_BUFFER_AGE: usize> { pub struct WinitSoftwareRenderer {
renderer: SoftwareRenderer<MAX_BUFFER_AGE>, renderer: SoftwareRenderer,
canvas: RefCell<Option<softbuffer::GraphicsContext>>, canvas: RefCell<Option<softbuffer::GraphicsContext>>,
} }
impl<const MAX_BUFFER_AGE: usize> super::WinitCompatibleRenderer impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
for WinitSoftwareRenderer<MAX_BUFFER_AGE>
{
const NAME: &'static str = "Software"; const NAME: &'static str = "Software";
fn new(window_adapter_weak: &Weak<dyn WindowAdapter>) -> Self { fn new(window_adapter_weak: &Weak<dyn WindowAdapter>) -> Self {
Self { Self {
renderer: SoftwareRenderer::new(window_adapter_weak.clone()), renderer: SoftwareRenderer::new(
i_slint_core::software_renderer::RepaintBufferType::NewBuffer,
window_adapter_weak.clone(),
),
canvas: Default::default(), canvas: Default::default(),
} }
} }

View file

@ -40,6 +40,24 @@ type PhysicalPoint = euclid::Point2D<i16, PhysicalPx>;
type DirtyRegion = PhysicalRect; type DirtyRegion = PhysicalRect;
/// This enum describes which parts of the buffer passed to the [`SoftwareRenderer`] may be re-used to speed up painting.
#[derive(PartialEq, Eq, Debug, Clone, Default)]
pub enum RepaintBufferType {
#[default]
/// The full window is always redrawn. No attempt at partial rendering will be made.
NewBuffer,
/// Only redraw the parts that have changed since the previous call to render().
///
/// This variant assumes that the same buffer is passed on every call to render() and
/// that it still contains the previously rendered frame.
ReusedBuffer,
/// Redraw the part that have changed since the last two frames were drawn.
///
/// This is used when using double buffering and swapping of the buffers.
SwappedBuffers,
}
/// This trait defines a bi-directional interface between Slint and your code to send lines to your screen, when using /// This trait defines a bi-directional interface between Slint and your code to send lines to your screen, when using
/// the [`SoftwareRenderer::render_by_line`] function. /// the [`SoftwareRenderer::render_by_line`] function.
/// ///
@ -76,43 +94,34 @@ pub trait LineBufferProvider {
/// 2. Using [`render_by_line()`](Self::render()) to render the window line by line. This /// 2. Using [`render_by_line()`](Self::render()) to render the window line by line. This
/// is only useful if the device does not have enough memory to render the whole window /// is only useful if the device does not have enough memory to render the whole window
/// in one single buffer /// in one single buffer
/// pub struct SoftwareRenderer {
/// ### `MAX_BUFFER_AGE`
///
/// The `MAX_BUFFER_AGE` parameter specifies how many buffers are being re-used.
/// This means that the buffer passed to the render functions still contains a rendering of
/// the window that was refreshed as least that amount of frame ago.
/// It will impact how much of the screen needs to be redrawn.
///
/// Typical value can be:
/// - **0:** No attempt at tracking dirty items will be made. The full screen is always redrawn.
/// - **1:** Only redraw the parts that have changed since the previous call to render.
/// This is assuming that the same buffer is passed on every call to render.
/// - **2:** Redraw the part that have changed during the two last frames.
/// This is assuming double buffering and swapping of the buffers.
pub struct SoftwareRenderer<const MAX_BUFFER_AGE: usize> {
partial_cache: RefCell<crate::item_rendering::PartialRenderingCache>, partial_cache: RefCell<crate::item_rendering::PartialRenderingCache>,
repaint_buffer_type: RepaintBufferType,
/// This is the area which we are going to redraw in the next frame, no matter if the items are dirty or not /// This is the area which we are going to redraw in the next frame, no matter if the items are dirty or not
force_dirty: Cell<crate::item_rendering::DirtyRegion>, force_dirty: Cell<crate::item_rendering::DirtyRegion>,
/// This is the area which was dirty on the previous frames, in case we do double buffering /// This is the area which was dirty on the previous frame.
/// /// Only used if repaint_buffer_type == RepaintBufferType::SwappedBuffers
/// We really only need MAX_BUFFER_AGE - 1 but that's not allowed because we cannot do operations with prev_frame_dirty: Cell<DirtyRegion>,
/// generic parameters
prev_frame_dirty: [Cell<DirtyRegion>; MAX_BUFFER_AGE],
window: Weak<dyn crate::window::WindowAdapter>, window: Weak<dyn crate::window::WindowAdapter>,
} }
impl<const MAX_BUFFER_AGE: usize> SoftwareRenderer<MAX_BUFFER_AGE> { impl SoftwareRenderer {
/// Create a new Renderer for a given window. /// Create a new Renderer for a given window.
/// ///
/// The `repaint_buffer_type` parameter specify what kind of buffer are passed to [`Self::render`]
///
/// The `window` parameter can be coming from [`Rc::new_cyclic()`](alloc::rc::Rc::new_cyclic()) /// The `window` parameter can be coming from [`Rc::new_cyclic()`](alloc::rc::Rc::new_cyclic())
/// since the `WindowAdapter` most likely own the Renderer /// since the `WindowAdapter` most likely own the Renderer
pub fn new(window: Weak<dyn crate::window::WindowAdapter>) -> Self { pub fn new(
repaint_buffer_type: RepaintBufferType,
window: Weak<dyn crate::window::WindowAdapter>,
) -> Self {
Self { Self {
window: window.clone(), window: window.clone(),
repaint_buffer_type,
partial_cache: Default::default(), partial_cache: Default::default(),
force_dirty: Default::default(), force_dirty: Default::default(),
prev_frame_dirty: [DirtyRegion::default(); MAX_BUFFER_AGE].map(|x| x.into()), prev_frame_dirty: Default::default(),
} }
} }
@ -123,20 +132,14 @@ impl<const MAX_BUFFER_AGE: usize> SoftwareRenderer<MAX_BUFFER_AGE> {
dirty_region: DirtyRegion, dirty_region: DirtyRegion,
screen_size: PhysicalSize, screen_size: PhysicalSize,
) -> DirtyRegion { ) -> DirtyRegion {
if MAX_BUFFER_AGE == 0 { match self.repaint_buffer_type {
PhysicalRect { origin: euclid::point2(0, 0), size: screen_size } RepaintBufferType::NewBuffer => {
} else if MAX_BUFFER_AGE == 1 { PhysicalRect { origin: euclid::point2(0, 0), size: screen_size }
dirty_region }
} else if MAX_BUFFER_AGE == 2 { RepaintBufferType::ReusedBuffer => dirty_region,
dirty_region.union(&self.prev_frame_dirty[0].replace(dirty_region)) RepaintBufferType::SwappedBuffers => {
} else { dirty_region.union(&self.prev_frame_dirty.replace(dirty_region))
let mut prev = dirty_region;
let mut union = dirty_region;
for x in self.prev_frame_dirty.iter().skip(1) {
prev = x.replace(prev);
union = union.union(&prev);
} }
union
} }
.intersection(&PhysicalRect { origin: euclid::point2(0, 0), size: screen_size }) .intersection(&PhysicalRect { origin: euclid::point2(0, 0), size: screen_size })
.unwrap_or_default() .unwrap_or_default()
@ -149,7 +152,7 @@ impl<const MAX_BUFFER_AGE: usize> SoftwareRenderer<MAX_BUFFER_AGE> {
/// be rendered. (eg: the previous dirty region in case of double buffering) /// be rendered. (eg: the previous dirty region in case of double buffering)
/// ///
/// returns the dirty region for this frame (not including the extra_draw_region) /// returns the dirty region for this frame (not including the extra_draw_region)
pub fn render(&self, buffer: &mut [impl TargetPixel], buffer_stride: usize) { pub fn render(&self, buffer: &mut [impl TargetPixel], pixel_stride: usize) {
let window = self.window.upgrade().expect("render() called on a destroyed Window"); let window = self.window.upgrade().expect("render() called on a destroyed Window");
let window_inner = WindowInner::from_pub(window.window()); let window_inner = WindowInner::from_pub(window.window());
let factor = ScaleFactor::new(window_inner.scale_factor()); let factor = ScaleFactor::new(window_inner.scale_factor());
@ -163,16 +166,13 @@ impl<const MAX_BUFFER_AGE: usize> SoftwareRenderer<MAX_BUFFER_AGE> {
window_item.background(), window_item.background(),
) )
} else { } else {
( (euclid::size2(pixel_stride as _, (buffer.len() / pixel_stride) as _), Brush::default())
euclid::size2(buffer_stride as _, (buffer.len() / buffer_stride) as _),
Brush::default(),
)
}; };
let buffer_renderer = SceneBuilder::new( let buffer_renderer = SceneBuilder::new(
size, size,
factor, factor,
window_inner, window_inner,
RenderToBuffer { buffer, stride: buffer_stride }, RenderToBuffer { buffer, stride: pixel_stride },
); );
let mut renderer = crate::item_rendering::PartialRenderer::new( let mut renderer = crate::item_rendering::PartialRenderer::new(
&self.partial_cache, &self.partial_cache,
@ -221,7 +221,7 @@ impl<const MAX_BUFFER_AGE: usize> SoftwareRenderer<MAX_BUFFER_AGE> {
/// ///
/// ```rust /// ```rust
/// # use i_slint_core::software_renderer::{LineBufferProvider, SoftwareRenderer, Rgb565Pixel}; /// # use i_slint_core::software_renderer::{LineBufferProvider, SoftwareRenderer, Rgb565Pixel};
/// # fn xxx<'a>(the_frame_buffer: &'a mut [Rgb565Pixel], display_width: usize, renderer: &SoftwareRenderer<0>) { /// # fn xxx<'a>(the_frame_buffer: &'a mut [Rgb565Pixel], display_width: usize, renderer: &SoftwareRenderer) {
/// struct FrameBuffer<'a>{ frame_buffer: &'a mut [Rgb565Pixel], stride: usize } /// struct FrameBuffer<'a>{ frame_buffer: &'a mut [Rgb565Pixel], stride: usize }
/// impl<'a> LineBufferProvider for FrameBuffer<'a> { /// impl<'a> LineBufferProvider for FrameBuffer<'a> {
/// type TargetPixel = Rgb565Pixel; /// type TargetPixel = Rgb565Pixel;
@ -263,7 +263,7 @@ impl<const MAX_BUFFER_AGE: usize> SoftwareRenderer<MAX_BUFFER_AGE> {
} }
#[doc(hidden)] #[doc(hidden)]
impl<const MAX_BUFFER_AGE: usize> Renderer for SoftwareRenderer<MAX_BUFFER_AGE> { impl Renderer for SoftwareRenderer {
fn text_size( fn text_size(
&self, &self,
font_request: crate::graphics::FontRequest, font_request: crate::graphics::FontRequest,
@ -328,11 +328,11 @@ impl<const MAX_BUFFER_AGE: usize> Renderer for SoftwareRenderer<MAX_BUFFER_AGE>
} }
} }
fn render_window_frame_by_line<const MAX_BUFFER_AGE: usize>( fn render_window_frame_by_line(
window: &WindowInner, window: &WindowInner,
background: Brush, background: Brush,
size: PhysicalSize, size: PhysicalSize,
renderer: &SoftwareRenderer<MAX_BUFFER_AGE>, renderer: &SoftwareRenderer,
mut line_buffer: impl LineBufferProvider, mut line_buffer: impl LineBufferProvider,
) { ) {
let mut scene = prepare_scene(window, size, renderer); let mut scene = prepare_scene(window, size, renderer);
@ -749,10 +749,10 @@ struct GradientCommand {
bottom_clip: PhysicalLength, bottom_clip: PhysicalLength,
} }
fn prepare_scene<const MAX_BUFFER_AGE: usize>( fn prepare_scene(
window: &WindowInner, window: &WindowInner,
size: PhysicalSize, size: PhysicalSize,
software_renderer: &SoftwareRenderer<MAX_BUFFER_AGE>, software_renderer: &SoftwareRenderer,
) -> Scene { ) -> Scene {
let factor = ScaleFactor::new(window.scale_factor()); let factor = ScaleFactor::new(window.scale_factor());
let prepare_scene = SceneBuilder::new(size, factor, window, PrepareScene::default()); let prepare_scene = SceneBuilder::new(size, factor, window, PrepareScene::default());
@ -1640,21 +1640,20 @@ impl<'a, T: ProcessScene> crate::item_rendering::ItemRenderer for SceneBuilder<'
/// This is a minimal adapter for a Window that doesn't have any other feature than rendering /// This is a minimal adapter for a Window that doesn't have any other feature than rendering
/// using the software renderer. /// using the software renderer.
/// pub struct MinimalSoftwareWindow {
/// The [`MAX_BUFFER_AGE`](SoftwareRenderer#max_buffer_age) generic parameter is forwarded to
/// the [`SoftwareRenderer`]
pub struct MinimalSoftwareWindow<const MAX_BUFFER_AGE: usize> {
window: Window, window: Window,
renderer: SoftwareRenderer<MAX_BUFFER_AGE>, renderer: SoftwareRenderer,
needs_redraw: Cell<bool>, needs_redraw: Cell<bool>,
} }
impl<const MAX_BUFFER_AGE: usize> MinimalSoftwareWindow<MAX_BUFFER_AGE> { impl MinimalSoftwareWindow {
/// Instantiate a new MinimalWindowAdaptor /// Instantiate a new MinimalWindowAdaptor
pub fn new() -> Rc<Self> { ///
/// The `repaint_buffer_type` parameter specify what kind of buffer are passed to the [`SoftwareRenderer`]
pub fn new(repaint_buffer_type: RepaintBufferType) -> Rc<Self> {
Rc::new_cyclic(|w: &Weak<Self>| Self { Rc::new_cyclic(|w: &Weak<Self>| Self {
window: Window::new(w.clone()), window: Window::new(w.clone()),
renderer: SoftwareRenderer::new(w.clone()), renderer: SoftwareRenderer::new(repaint_buffer_type, w.clone()),
needs_redraw: Default::default(), needs_redraw: Default::default(),
}) })
} }
@ -1665,10 +1664,7 @@ impl<const MAX_BUFFER_AGE: usize> MinimalSoftwareWindow<MAX_BUFFER_AGE> {
/// in that callback. /// in that callback.
/// ///
/// Return true if something was redrawn. /// Return true if something was redrawn.
pub fn draw_if_needed( pub fn draw_if_needed(&self, render_callback: impl FnOnce(&SoftwareRenderer)) -> bool {
&self,
render_callback: impl FnOnce(&SoftwareRenderer<MAX_BUFFER_AGE>),
) -> bool {
if self.needs_redraw.replace(false) { if self.needs_redraw.replace(false) {
render_callback(&self.renderer); render_callback(&self.renderer);
true true
@ -1678,9 +1674,7 @@ impl<const MAX_BUFFER_AGE: usize> MinimalSoftwareWindow<MAX_BUFFER_AGE> {
} }
} }
impl<const MAX_BUFFER_AGE: usize> crate::window::WindowAdapterSealed impl crate::window::WindowAdapterSealed for MinimalSoftwareWindow {
for MinimalSoftwareWindow<MAX_BUFFER_AGE>
{
fn request_redraw(&self) { fn request_redraw(&self) {
self.needs_redraw.set(true); self.needs_redraw.set(true);
} }
@ -1696,13 +1690,13 @@ impl<const MAX_BUFFER_AGE: usize> crate::window::WindowAdapterSealed
} }
} }
impl<const MAX_BUFFER_AGE: usize> WindowAdapter for MinimalSoftwareWindow<MAX_BUFFER_AGE> { impl WindowAdapter for MinimalSoftwareWindow {
fn window(&self) -> &Window { fn window(&self) -> &Window {
&self.window &self.window
} }
} }
impl<const MAX_BUFFER_AGE: usize> core::ops::Deref for MinimalSoftwareWindow<MAX_BUFFER_AGE> { impl core::ops::Deref for MinimalSoftwareWindow {
type Target = Window; type Target = Window;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.window &self.window

View file

@ -19,7 +19,7 @@ use i_slint_core::{
}; };
pub struct SwrTestingBackend { pub struct SwrTestingBackend {
window: Rc<MinimalSoftwareWindow<1>>, window: Rc<MinimalSoftwareWindow>,
} }
impl i_slint_core::platform::Platform for SwrTestingBackend { impl i_slint_core::platform::Platform for SwrTestingBackend {
@ -32,8 +32,10 @@ impl i_slint_core::platform::Platform for SwrTestingBackend {
} }
} }
pub fn init_swr() -> Rc<MinimalSoftwareWindow<1>> { pub fn init_swr() -> Rc<MinimalSoftwareWindow> {
let window = MinimalSoftwareWindow::new(); let window = MinimalSoftwareWindow::new(
i_slint_core::software_renderer::RepaintBufferType::ReusedBuffer,
);
i_slint_core::platform::set_platform(Box::new(SwrTestingBackend { window: window.clone() })) i_slint_core::platform::set_platform(Box::new(SwrTestingBackend { window: window.clone() }))
.unwrap(); .unwrap();
@ -52,7 +54,7 @@ pub fn image_buffer(path: &str) -> Result<SharedPixelBuffer<Rgb8Pixel>, image::I
}) })
} }
pub fn screenshot(window: Rc<MinimalSoftwareWindow<1>>) -> SharedPixelBuffer<Rgb8Pixel> { pub fn screenshot(window: Rc<MinimalSoftwareWindow>) -> SharedPixelBuffer<Rgb8Pixel> {
let size = window.size(); let size = window.size();
let width = size.width; let width = size.width;
let height = size.height; let height = size.height;
@ -184,14 +186,14 @@ fn compare_images(
result result
} }
pub fn assert_with_render(path: &str, window: Rc<MinimalSoftwareWindow<1>>) { pub fn assert_with_render(path: &str, window: Rc<MinimalSoftwareWindow>) {
let rendering = screenshot(window); let rendering = screenshot(window);
if let Err(reason) = compare_images(path, &rendering) { if let Err(reason) = compare_images(path, &rendering) {
panic!("Image comparison failure for {path}: {reason}"); panic!("Image comparison failure for {path}: {reason}");
} }
} }
pub fn assert_with_render_by_line(path: &str, window: Rc<MinimalSoftwareWindow<1>>) { pub fn assert_with_render_by_line(path: &str, window: Rc<MinimalSoftwareWindow>) {
let s = window.size(); let s = window.size();
let mut rendering = SharedPixelBuffer::<Rgb8Pixel>::new(s.width, s.height); let mut rendering = SharedPixelBuffer::<Rgb8Pixel>::new(s.width, s.height);
@ -213,7 +215,7 @@ pub fn assert_with_render_by_line(path: &str, window: Rc<MinimalSoftwareWindow<1
} }
pub fn screenshot_render_by_line( pub fn screenshot_render_by_line(
window: Rc<MinimalSoftwareWindow<1>>, window: Rc<MinimalSoftwareWindow>,
region: Option<IntRect>, region: Option<IntRect>,
buffer: &mut SharedPixelBuffer<Rgb8Pixel>, buffer: &mut SharedPixelBuffer<Rgb8Pixel>,
) { ) {
@ -236,7 +238,7 @@ pub fn screenshot_render_by_line(
}); });
} }
pub fn save_screenshot(path: &str, window: Rc<MinimalSoftwareWindow<1>>) { pub fn save_screenshot(path: &str, window: Rc<MinimalSoftwareWindow>) {
let buffer = screenshot(window.clone()); let buffer = screenshot(window.clone());
image::save_buffer( image::save_buffer(
path, path,