Skip to content

Commit

Permalink
Software renderer: partial rendering with winit and fix non-opaque ba…
Browse files Browse the repository at this point in the history
…ckground

Implement the partial rendering with winit and our software renderer.

When the background is not opaque, we must still initialize all the
pixel with 0 otherwise we blend over the previous frame.
(That wasn't visible before because the buffer was always empty)
  • Loading branch information
ogoffart committed Sep 13, 2023
1 parent 196f14e commit 46ec787
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 29 deletions.
3 changes: 2 additions & 1 deletion internal/backends/winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ renderer-femtovg = ["i-slint-renderer-femtovg"]
renderer-skia = ["i-slint-renderer-skia"]
renderer-skia-opengl = ["renderer-skia", "i-slint-renderer-skia/opengl"]
renderer-skia-vulkan = ["renderer-skia", "i-slint-renderer-skia/vulkan"]
renderer-software = ["softbuffer", "imgref", "rgb", "i-slint-core/software-renderer-systemfonts"]
renderer-software = ["softbuffer", "imgref", "rgb", "i-slint-core/software-renderer-systemfonts", "bytemuck"]
accessibility = ["accesskit", "accesskit_winit"]
default = []

Expand Down Expand Up @@ -61,6 +61,7 @@ i-slint-renderer-skia = { version = "=1.3.0", path = "../../renderers/skia", opt
softbuffer = { version = "0.3.1", optional = true }
imgref = { version = "1.6.1", optional = true }
rgb = { version = "0.8.27", optional = true }
bytemuck = { version = "1.13.1", optional = true, features = ["derive"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features=["HtmlInputElement", "HtmlCanvasElement", "Window", "Document", "Event", "KeyboardEvent", "InputEvent", "CompositionEvent", "DomStringMap", "ClipboardEvent", "DataTransfer"] }
Expand Down
3 changes: 3 additions & 0 deletions internal/backends/winit/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ fn process_window_event(
WindowEvent::ThemeChanged(theme) => {
window.set_dark_color_scheme(theme == winit::window::Theme::Dark)
}
WindowEvent::Occluded(x) => {
window.renderer.occluded(x);
}
_ => {}
}
Ok(())
Expand Down
4 changes: 4 additions & 0 deletions internal/backends/winit/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub use winit;

/// Internal type used by the winit backend for thread communication and window system updates.
#[non_exhaustive]
#[derive(Debug)]
pub enum SlintUserEvent {
CustomEvent { event: CustomEvent },
}
Expand All @@ -39,6 +40,9 @@ mod renderer {
fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>;

fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;

// Got WindowEvent::Occluded
fn occluded(&self, _: bool) {}
}

#[cfg(feature = "renderer-femtovg")]
Expand Down
96 changes: 80 additions & 16 deletions internal/backends/winit/renderer/sw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

//! Delegate the rendering to the [`i_slint_core::software_renderer::SoftwareRenderer`]

use core::num::NonZeroU32;
use core::ops::DerefMut;
use i_slint_core::graphics::Rgb8Pixel;
use i_slint_core::platform::PlatformError;
use i_slint_core::software_renderer::PremultipliedRgbaColor;
pub use i_slint_core::software_renderer::SoftwareRenderer;
use i_slint_core::software_renderer::{PremultipliedRgbaColor, RepaintBufferType, TargetPixel};
use std::cell::RefCell;

pub struct WinitSoftwareRenderer {
Expand All @@ -15,6 +17,51 @@ pub struct WinitSoftwareRenderer {
surface: RefCell<softbuffer::Surface>,
}

#[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 super::WinitCompatibleRenderer for WinitSoftwareRenderer {
fn new(
window_builder: winit::window::WindowBuilder,
Expand Down Expand Up @@ -63,19 +110,19 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
.buffer_mut()
.map_err(|e| format!("Error retrieving softbuffer rendering buffer: {e}"))?;

if std::env::var_os("SLINT_LINE_BY_LINE").is_none() {
let mut buffer = vec![
PremultipliedRgbaColor::default();
width.get() as usize * height.get() as usize
];
self.renderer.render(buffer.as_mut_slice(), width.get() as usize);
for (i, pixel) in buffer.into_iter().enumerate() {
target_buffer[i] = (pixel.alpha as u32) << 24
| ((pixel.red as u32) << 16)
| ((pixel.green as u32) << 8)
| (pixel.blue as u32);
}
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>,
Expand All @@ -101,15 +148,32 @@ impl super::WinitCompatibleRenderer for WinitSoftwareRenderer {
self.renderer.render_by_line(FrameBuffer {
buffer: &mut target_buffer,
line: vec![Default::default(); width.get() as usize],
});
})
};

target_buffer.present().map_err(|e| format!("Error presenting softbuffer buffer: {e}"))?;

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);
}
}
2 changes: 1 addition & 1 deletion internal/backends/winit/winitwindowadapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ pub struct WinitWindowAdapter {
shown: Cell<bool>,
window_level: Cell<winit::window::WindowLevel>,

renderer: Box<dyn WinitCompatibleRenderer>,
pub(crate) renderer: Box<dyn WinitCompatibleRenderer>,
// We cache the size because winit_window.inner_size() can return different value between calls (eg, on X11)
// And we wan see the newer value before the Resized event was received, leading to inconsistencies
size: Cell<PhysicalSize>,
Expand Down
25 changes: 15 additions & 10 deletions internal/core/software_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ impl SoftwareRenderer {
///
/// This may clear the internal caches
pub fn set_repaint_buffer_type(&self, repaint_buffer_type: RepaintBufferType) {
self.repaint_buffer_type.set(repaint_buffer_type);
self.partial_cache.borrow_mut().clear();
if self.repaint_buffer_type.replace(repaint_buffer_type) != repaint_buffer_type {
self.partial_cache.borrow_mut().clear();
}
}

/// Returns the kind of buffer that must be passed to [`Self::render`]
Expand Down Expand Up @@ -246,13 +247,16 @@ impl SoftwareRenderer {
LogicalLength::zero(),
);

if !background.is_transparent() {
// FIXME: gradient
renderer
.actual_renderer
.processor
.process_rectangle(to_draw, background.color().into());
let mut bg = TargetPixel::background();
// TODO: gradient background
TargetPixel::blend(&mut bg, background.color().into());
for line in to_draw.min_y()..to_draw.max_y() {
let begin = line as usize * pixel_stride + to_draw.origin.x as usize;
renderer.actual_renderer.processor.buffer
[begin..begin + to_draw.width() as usize]
.fill(bg);
}

for (component, origin) in components {
crate::item_rendering::render_component_items(
component,
Expand Down Expand Up @@ -525,8 +529,9 @@ fn render_window_frame_by_line(

debug_assert!(scene.current_line >= dirty_region.origin.y_length());

let mut background_color = TargetPixel::background();
// FIXME gradient
let background_color = background.color().into();
TargetPixel::blend(&mut background_color, background.color().into());

while scene.current_line < dirty_region.origin.y_length() + dirty_region.size.height_length() {
line_buffer.process_line(
Expand All @@ -535,7 +540,7 @@ fn render_window_frame_by_line(
|line_buffer| {
let offset = dirty_region.min_x() as usize;

TargetPixel::blend_slice(line_buffer, background_color);
line_buffer.fill(background_color);
for span in scene.items[0..scene.current_items_index].iter().rev() {
debug_assert!(scene.current_line >= span.pos.y_length());
debug_assert!(
Expand Down
9 changes: 9 additions & 0 deletions internal/core/software_renderer/draw_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ pub trait TargetPixel: Sized + Copy {
}
/// Create a pixel from the red, gree, blue component in the range 0..=255
fn from_rgb(red: u8, green: u8, blue: u8) -> Self;

/// Pixel which will be filled as the background in case the slint view has transparency
fn background() -> Self {
Self::from_rgb(0, 0, 0)
}
}

impl TargetPixel for crate::graphics::image::Rgb8Pixel {
Expand Down Expand Up @@ -481,6 +486,10 @@ impl TargetPixel for PremultipliedRgbaColor {
fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self { red: r, green: g, blue: b, alpha: 255 }
}

fn background() -> Self {
Self { red: 0, green: 0, blue: 0, alpha: 0 }
}
}

/// A 16bit pixel that has 5 red bits, 6 green bits and 5 blue bits
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial

export component TestCase inherits Window {
width: 64px;
height: 64px;

background: #2360e62b;

GridLayout {
spacing: 3.5px;
padding: 5.33px;
Rectangle { background: #4d8c; }
Rectangle { background: #4d8; opacity: 0.8; }
Row {}
Text { text: "Hi!"; color: #99aa3380; }
Text { text: "Hi!"; color: #9a3; opacity: 0.5; }
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion tests/screenshots/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,12 @@ pub fn assert_with_render_by_line(path: &str, window: Rc<MinimalSoftwareWindow>)
let region = euclid::rect(s.width / 4, s.height / 4, s.width / 2, s.height / 2).cast::<usize>();
for y in region.y_range() {
let stride = rendering.width() as usize;
rendering.make_mut_slice()[y * stride..][region.x_range()].fill(Default::default());
// fill with garbage
rendering.make_mut_slice()[y * stride..][region.x_range()].fill(Rgb8Pixel::new(
((y << 3) & 0xff) as u8,
0,
255,
));
}
screenshot_render_by_line(window, Some(region.cast()), &mut rendering);
if let Err(reason) = compare_images(path, &rendering) {
Expand Down

0 comments on commit 46ec787

Please sign in to comment.