diff --git a/api/cpp/Cargo.toml b/api/cpp/Cargo.toml index 7972c615cd3..71acb7b8f43 100644 --- a/api/cpp/Cargo.toml +++ b/api/cpp/Cargo.toml @@ -46,7 +46,7 @@ system-testing = ["i-slint-backend-selector/system-testing"] std = ["i-slint-core/default", "i-slint-core/image-default-formats", "i-slint-backend-selector"] freestanding = ["i-slint-core/libm", "i-slint-core/unsafe-single-threaded"] -experimental = [] +experimental = ["i-slint-core/experimental"] default = ["std", "backend-winit", "renderer-femtovg", "backend-qt"] diff --git a/api/cpp/cbindgen.rs b/api/cpp/cbindgen.rs index c08cf37f839..9fead07d2e1 100644 --- a/api/cpp/cbindgen.rs +++ b/api/cpp/cbindgen.rs @@ -327,6 +327,7 @@ fn gen_corelib( "SortOrder", "BitmapFont", "PhysicalRegion", + "CompositionMode", ] .iter() .chain(items.iter()) @@ -897,7 +898,7 @@ fn gen_platform( .with_after_include( r" namespace slint::platform { struct Rgb565Pixel; } -namespace slint::cbindgen_private { struct WindowProperties; using slint::platform::Rgb565Pixel; } +namespace slint::cbindgen_private { struct WindowProperties; using slint::platform::Rgb565Pixel; using slint::cbindgen_private::types::TexturePixelFormat; } ", ) .generate() diff --git a/api/cpp/include/slint-platform.h b/api/cpp/include/slint-platform.h index 844f54f83af..b3b01f80a7c 100644 --- a/api/cpp/include/slint-platform.h +++ b/api/cpp/include/slint-platform.h @@ -538,6 +538,71 @@ struct Rgb565Pixel friend bool operator==(const Rgb565Pixel &lhs, const Rgb565Pixel &rhs) = default; }; +# ifdef SLINT_FEATURE_EXPERIMENTAL + +using cbindgen_private::CompositionMode; +using cbindgen_private::types::TexturePixelFormat; + +/// This structure describes the properties of a texture for blending with +/// TargetPixelBuffer::draw_texture(). +struct Texture +{ + /// A reference to the pixel bytes of the texture. These bytes are in the format specified by + /// `pixel_format`. + std::span bytes; + /// The pixel format of the texture. + TexturePixelFormat pixel_format; + /// The number of pixels per line. + uint16_t pixel_stride; + /// The width of the texture in pixels. + uint16_t width; + /// The height of the texture in pixels. + uint16_t height; + /// The delta to apply to the source x coordinate between pixels when drawing the texture. + /// This is used when scaling the texture. The delta is specified in 8:8 fixed point format. + uint16_t delta_x; + /// The delta to apply to the source y coordinate between pixels when drawing the texture. + /// This is used when scaling the texture. The delta is specified in 8:8 fixed point format. + uint16_t delta_y; + /// The offset within the texture to start reading pixels from in the x direction. The + /// offset is specified in 12:4 fixed point format. + uint16_t source_offset_x; + /// The offset within the texture to start reading pixels from in the y direction. The + /// offset is specified in 12:4 fixed point format. + uint16_t source_offset_y; +}; + +/// Abstract base class for a target pixel buffer where certain drawing operations can be delegated. +/// Use this to implement support for hardware accelerators such as DMA2D, PPA, or PXP on +/// Microcontrollers. +template +struct TargetPixelBuffer +{ + virtual ~TargetPixelBuffer() { } + + /// Returns a span of pixels for the specified line number. + virtual std::span line_slice(std::size_t line_number) = 0; + /// Returns the number of lines in the buffer. This is the height of the buffer in pixels. + virtual std::size_t num_lines() = 0; + + /// Fill a rectangle at the specified pixel coordinates with the given color. Return true + /// if the operation succeeded; false otherwise; + virtual bool fill_rectangle(int16_t x, int16_t y, int16_t width, int16_t height, + const RgbaColor &premultiplied_color, + CompositionMode composition_mode) = 0; + + /// Draw a portion of provided texture to the specified pixel coordinates. This may + /// be called multiple times for a given texture, for example when clipping. Typically + /// y is identicaly with span_y, but when clipping, span_y refers to the y coordinate of + /// the texture if it's unclipped. Each pixel of the texture is to be blended with the given + /// colorize color as well as the alpha value. + virtual bool draw_texture(int16_t x, int16_t y, int16_t width, int16_t height, int16_t span_y, + const Texture &texture, const RgbaColor &colorize, + uint8_t alpha, int screen_rotation_degrees, + CompositionMode composition_mode) = 0; +}; +# endif + /// Slint's software renderer. /// /// To be used as a template parameter of the WindowAdapter. @@ -680,6 +745,118 @@ class SoftwareRenderer : public AbstractRenderer return PhysicalRegion { r }; } +# ifdef SLINT_FEATURE_EXPERIMENTAL + /// Renders into the given TargetPixelBuffer. + PhysicalRegion render(TargetPixelBuffer *buffer) const + { + cbindgen_private::CppRgb8TargetPixelBuffer buffer_wrapper { + .user_data = buffer, + .line_slice = + [](void *self, uintptr_t line_number, Rgb8Pixel **slice_ptr, + uintptr_t *slice_len) { + auto *buffer = reinterpret_cast *>(self); + auto slice = buffer->line_slice(line_number); + *slice_ptr = slice.data(); + *slice_len = slice.size(); + }, + .num_lines = + [](void *self) { + auto *buffer = reinterpret_cast *>(self); + return buffer->num_lines(); + }, + .fill_rectangle = + [](void *self, int16_t x, int16_t y, int16_t width, int16_t height, uint8_t red, + uint8_t green, uint8_t blue, uint8_t alpha, + CompositionMode composition_mode) { + auto *buffer = reinterpret_cast *>(self); + return buffer->fill_rectangle( + x, y, width, height, + Color::from_argb_uint8(alpha, red, green, blue), composition_mode); + }, + .draw_texture = + [](void *self, int16_t x, int16_t y, int16_t width, int16_t height, + int16_t span_y, const cbindgen_private::CppInternalTexture *internal_texture, + uint32_t colorize, uint8_t alpha, int32_t screen_rotation_degrees, + CompositionMode composition_mode) { + auto *buffer = reinterpret_cast *>(self); + Texture texture { + .bytes = std::span { internal_texture->bytes, + internal_texture->bytes_len }, + .pixel_format = internal_texture->pixel_format, + .pixel_stride = internal_texture->pixel_stride, + .width = internal_texture->width, + .height = internal_texture->height, + .delta_x = internal_texture->delta_x, + .delta_y = internal_texture->delta_y, + .source_offset_x = internal_texture->source_offset_x, + .source_offset_y = internal_texture->source_offset_y, + }; + return buffer->draw_texture(x, y, width, height, span_y, texture, + Color::from_argb_encoded(colorize), alpha, + screen_rotation_degrees, composition_mode); + } + }; + auto r = + cbindgen_private::slint_software_renderer_render_accel_rgb8(inner, &buffer_wrapper); + return PhysicalRegion { r }; + } + + /// Renders into the given TargetPixelBuffer. + PhysicalRegion render(TargetPixelBuffer *buffer) const + { + cbindgen_private::CppRgb565TargetPixelBuffer buffer_wrapper { + .user_data = buffer, + .line_slice = + [](void *self, uintptr_t line_number, uint16_t **slice_ptr, + uintptr_t *slice_len) { + auto *buffer = reinterpret_cast *>(self); + auto slice = buffer->line_slice(line_number); + *slice_ptr = reinterpret_cast(slice.data()); + *slice_len = slice.size(); + }, + .num_lines = + [](void *self) { + auto *buffer = reinterpret_cast *>(self); + return buffer->num_lines(); + }, + .fill_rectangle = + [](void *self, int16_t x, int16_t y, int16_t width, int16_t height, uint8_t red, + uint8_t green, uint8_t blue, uint8_t alpha, + CompositionMode composition_mode) { + auto *buffer = reinterpret_cast *>(self); + return buffer->fill_rectangle( + x, y, width, height, + Color::from_argb_uint8(alpha, red, green, blue), composition_mode); + }, + .draw_texture = + [](void *self, int16_t x, int16_t y, int16_t width, int16_t height, + int16_t span_y, const cbindgen_private::CppInternalTexture *internal_texture, + uint32_t colorize, uint8_t alpha, int32_t screen_rotation_degrees, + CompositionMode composition_mode) { + auto *buffer = reinterpret_cast *>(self); + Texture texture { + .bytes = std::span { internal_texture->bytes, + internal_texture->bytes_len }, + .pixel_format = internal_texture->pixel_format, + .pixel_stride = internal_texture->pixel_stride, + .width = internal_texture->width, + .height = internal_texture->height, + .delta_x = internal_texture->delta_x, + .delta_y = internal_texture->delta_y, + .source_offset_x = internal_texture->source_offset_x, + .source_offset_y = internal_texture->source_offset_y, + }; + return buffer->draw_texture(x, y, width, height, span_y, texture, + Color::from_argb_encoded(colorize), alpha, + screen_rotation_degrees, composition_mode); + } + }; + auto r = cbindgen_private::slint_software_renderer_render_accel_rgb565(inner, + &buffer_wrapper); + return PhysicalRegion { r }; + } +# endif + /// Render the window scene into an RGB 565 encoded pixel buffer /// /// The buffer must be at least as large as the associated slint::Window diff --git a/api/cpp/platform.rs b/api/cpp/platform.rs index d771e0f4bbf..22fc202f59c 100644 --- a/api/cpp/platform.rs +++ b/api/cpp/platform.rs @@ -360,6 +360,274 @@ mod software_renderer { }; use i_slint_core::SharedVector; + #[cfg(feature = "experimental")] + use i_slint_core::software_renderer::{ + CompositionMode, PremultipliedRgbaColor, RenderingRotation, TargetPixelBuffer, Texture, + TexturePixelFormat, + }; + + #[cfg(feature = "experimental")] + type CppTargetPixelBufferUserData = *mut c_void; + + #[repr(C)] + #[cfg(feature = "experimental")] + pub struct CppInternalTexture { + pub bytes: *const u8, + pub bytes_len: usize, + pub pixel_format: TexturePixelFormat, + pub pixel_stride: u16, + pub width: u16, + pub height: u16, + pub delta_x: u16, + pub delta_y: u16, + pub source_offset_x: u16, + pub source_offset_y: u16, + } + + #[repr(C)] + #[cfg(feature = "experimental")] + pub struct CppRgb8TargetPixelBuffer { + user_data: CppTargetPixelBufferUserData, + line_slice: unsafe extern "C" fn( + CppTargetPixelBufferUserData, + usize, + slice_ptr: &mut *mut Rgb8Pixel, + slice_len: *mut usize, + ), + num_lines: unsafe extern "C" fn(CppTargetPixelBufferUserData) -> usize, + fill_rectangle: unsafe extern "C" fn( + CppTargetPixelBufferUserData, + i16, + i16, + i16, + i16, + u8, + u8, + u8, + u8, + CompositionMode, + ) -> bool, + draw_texture: unsafe extern "C" fn( + CppTargetPixelBufferUserData, + i16, + i16, + i16, + i16, + i16, + &CppInternalTexture, + u32, + u8, + i32, + CompositionMode, + ) -> bool, + } + + #[cfg(feature = "experimental")] + impl TargetPixelBuffer for CppRgb8TargetPixelBuffer { + type TargetPixel = Rgb8Pixel; + + fn line_slice(&mut self, line_number: usize) -> &mut [Self::TargetPixel] { + unsafe { + let mut data = core::ptr::null_mut(); + let mut len = 0; + (self.line_slice)(self.user_data, line_number, &mut data, &mut len); + core::slice::from_raw_parts_mut(data, len) + } + } + + fn num_lines(&self) -> usize { + unsafe { (self.num_lines)(self.user_data) } + } + + fn fill_rectangle( + &mut self, + x: i16, + y: i16, + width: i16, + height: i16, + color: PremultipliedRgbaColor, + composition_mode: CompositionMode, + ) -> bool { + unsafe { + (self.fill_rectangle)( + self.user_data, + x, + y, + width, + height, + color.red, + color.green, + color.blue, + color.alpha, + composition_mode, + ) + } + } + + fn draw_texture( + &mut self, + x: i16, + y: i16, + width: i16, + height: i16, + span_y: i16, + texture: Texture<'_>, + colorize: u32, + alpha: u8, + rotation: RenderingRotation, + composition_mode: CompositionMode, + ) -> bool { + unsafe { + (self.draw_texture)( + self.user_data, + x, + y, + width, + height, + span_y, + &CppInternalTexture { + bytes: texture.bytes.as_ptr(), + bytes_len: texture.bytes.len(), + pixel_format: texture.pixel_format, + pixel_stride: texture.pixel_stride, + width: texture.width, + height: texture.height, + delta_x: texture.delta_x, + delta_y: texture.delta_y, + source_offset_x: texture.source_offset_x, + source_offset_y: texture.source_offset_y, + }, + colorize, + alpha, + rotation.angle() as i32, + composition_mode, + ) + } + } + } + + #[cfg(feature = "experimental")] + #[repr(C)] + pub struct CppRgb565TargetPixelBuffer { + user_data: CppTargetPixelBufferUserData, + line_slice: unsafe extern "C" fn( + CppTargetPixelBufferUserData, + usize, + slice_ptr: &mut *mut u16, + slice_len: *mut usize, + ), + num_lines: unsafe extern "C" fn(CppTargetPixelBufferUserData) -> usize, + fill_rectangle: unsafe extern "C" fn( + CppTargetPixelBufferUserData, + i16, + i16, + i16, + i16, + u8, + u8, + u8, + u8, + CompositionMode, + ) -> bool, + draw_texture: unsafe extern "C" fn( + CppTargetPixelBufferUserData, + i16, + i16, + i16, + i16, + i16, + &CppInternalTexture, + u32, + u8, + i32, + CompositionMode, + ) -> bool, + } + + #[cfg(feature = "experimental")] + impl TargetPixelBuffer for CppRgb565TargetPixelBuffer { + type TargetPixel = Rgb565Pixel; + + fn line_slice(&mut self, line_number: usize) -> &mut [Self::TargetPixel] { + unsafe { + let mut data = core::ptr::null_mut(); + let mut len = 0; + (self.line_slice)(self.user_data, line_number, &mut data, &mut len); + core::slice::from_raw_parts_mut(data as *mut Rgb565Pixel, len) + } + } + + fn num_lines(&self) -> usize { + unsafe { (self.num_lines)(self.user_data) } + } + + fn fill_rectangle( + &mut self, + x: i16, + y: i16, + width: i16, + height: i16, + color: PremultipliedRgbaColor, + composition_mode: CompositionMode, + ) -> bool { + unsafe { + (self.fill_rectangle)( + self.user_data, + x, + y, + width, + height, + color.red, + color.green, + color.blue, + color.alpha, + composition_mode, + ) + } + } + + fn draw_texture( + &mut self, + x: i16, + y: i16, + width: i16, + height: i16, + span_y: i16, + texture: Texture<'_>, + colorize: u32, + alpha: u8, + rotation: RenderingRotation, + composition_mode: CompositionMode, + ) -> bool { + unsafe { + (self.draw_texture)( + self.user_data, + x, + y, + width, + height, + span_y, + &CppInternalTexture { + bytes: texture.bytes.as_ptr(), + bytes_len: texture.bytes.len(), + pixel_format: texture.pixel_format, + pixel_stride: texture.pixel_stride, + width: texture.width, + height: texture.height, + delta_x: texture.delta_x, + delta_y: texture.delta_y, + source_offset_x: texture.source_offset_x, + source_offset_y: texture.source_offset_y, + }, + colorize, + alpha, + rotation.angle() as i32, + composition_mode, + ) + } + } + } + #[no_mangle] pub unsafe extern "C" fn slint_software_renderer_new( buffer_age: u32, @@ -391,6 +659,26 @@ mod software_renderer { renderer.render(buffer, pixel_stride) } + #[cfg(feature = "experimental")] + #[no_mangle] + pub unsafe extern "C" fn slint_software_renderer_render_accel_rgb8( + r: SoftwareRendererOpaque, + buffer: *mut CppRgb8TargetPixelBuffer, + ) -> PhysicalRegion { + let renderer = &*(r as *const SoftwareRenderer); + unsafe { renderer.render_into_buffer(&mut *buffer) } + } + + #[cfg(feature = "experimental")] + #[no_mangle] + pub unsafe extern "C" fn slint_software_renderer_render_accel_rgb565( + r: SoftwareRendererOpaque, + buffer: *mut CppRgb565TargetPixelBuffer, + ) -> PhysicalRegion { + let renderer = &*(r as *const SoftwareRenderer); + unsafe { renderer.render_into_buffer(&mut *buffer) } + } + #[no_mangle] pub unsafe extern "C" fn slint_software_renderer_render_rgb565( r: SoftwareRendererOpaque, diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 4928aeed7a7..21160eeb08e 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -942,7 +942,7 @@ fn embed_resource( init: Some(format!( "{{ .rect = {{ {r_x}, {r_y}, {r_w}, {r_h} }}, - .format = slint::cbindgen_private::types::PixelFormat::{format}, + .format = slint::cbindgen_private::types::TexturePixelFormat::{format}, .color = {color}, .index = 0, }}" diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 663b27a4699..e9ad4edb039 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -65,12 +65,12 @@ impl quote::ToTokens for crate::embedded_resources::PixelFormat { fn to_tokens(&self, tokens: &mut TokenStream) { use crate::embedded_resources::PixelFormat::*; let tks = match self { - Rgb => quote!(sp::PixelFormat::Rgb), - Rgba => quote!(sp::PixelFormat::Rgba), + Rgb => quote!(sp::TexturePixelFormat::Rgb), + Rgba => quote!(sp::TexturePixelFormat::Rgba), RgbaPremultiplied => { - quote!(sp::PixelFormat::RgbaPremultiplied) + quote!(sp::TexturePixelFormat::RgbaPremultiplied) } - AlphaMap(_) => quote!(sp::PixelFormat::AlphaMap), + AlphaMap(_) => quote!(sp::TexturePixelFormat::AlphaMap), }; tokens.extend(tks); } diff --git a/internal/core/Cargo.toml b/internal/core/Cargo.toml index f87d831dd4f..0d0b19c5465 100644 --- a/internal/core/Cargo.toml +++ b/internal/core/Cargo.toml @@ -45,6 +45,8 @@ shared-fontdb = ["i-slint-common/shared-fontdb"] raw-window-handle-06 = ["dep:raw-window-handle-06"] +experimental = [] + default = ["std", "unicode"] [dependencies] diff --git a/internal/core/graphics.rs b/internal/core/graphics.rs index 981553c4a00..8d9b2567671 100644 --- a/internal/core/graphics.rs +++ b/internal/core/graphics.rs @@ -214,6 +214,21 @@ impl From for RequestedGraphicsAPI { } } +/// This enum describes the how pixels from a source are merged with the pixels in a destination image. +/// This is a sub-set of the standard [Porter-Duff](https://en.wikipedia.org/wiki/Alpha_compositing) modes. +#[repr(u8)] +#[allow(dead_code)] +#[derive(Default, Copy, Clone, Debug)] +#[non_exhaustive] +pub enum CompositionMode { + /// Only pixels from the source target are drawn. + Source, + /// The source is placed over the destination. + #[default] + SourceOver, + // TODO: maybe add more modes (e.g. xor, plus darker, etc.) +} + /// Internal module for use by cbindgen and the C++ platform API layer. #[cfg(feature = "ffi")] pub mod ffi { diff --git a/internal/core/graphics/image.rs b/internal/core/graphics/image.rs index f7ebf629452..fe4127fe545 100644 --- a/internal/core/graphics/image.rs +++ b/internal/core/graphics/image.rs @@ -224,8 +224,8 @@ impl PartialEq for SharedImageBuffer { #[repr(u8)] #[derive(Clone, PartialEq, Debug, Copy)] -/// The pixel format of a StaticTexture -pub enum PixelFormat { +/// The pixel format used for textures. +pub enum TexturePixelFormat { /// red, green, blue. 24bits. Rgb, /// Red, green, blue, alpha. 32bits. @@ -241,15 +241,15 @@ pub enum PixelFormat { SignedDistanceField, } -impl PixelFormat { +impl TexturePixelFormat { /// The number of bytes in a pixel pub fn bpp(self) -> usize { match self { - PixelFormat::Rgb => 3, - PixelFormat::Rgba => 4, - PixelFormat::RgbaPremultiplied => 4, - PixelFormat::AlphaMap => 1, - PixelFormat::SignedDistanceField => 1, + TexturePixelFormat::Rgb => 3, + TexturePixelFormat::Rgba => 4, + TexturePixelFormat::RgbaPremultiplied => 4, + TexturePixelFormat::AlphaMap => 1, + TexturePixelFormat::SignedDistanceField => 1, } } } @@ -261,7 +261,7 @@ pub struct StaticTexture { /// The position and size of the texture within the image pub rect: IntRect, /// The pixel format of this texture - pub format: PixelFormat, + pub format: TexturePixelFormat, /// The color, for the alpha map ones pub color: crate::Color, /// index in the data array @@ -435,7 +435,7 @@ impl ImageInner { let slice = &mut slice[(rect.min_y() + y) * stride..][rect.x_range()]; let source = &ts.data[t.index + y * rect.width() * t.format.bpp()..]; match t.format { - PixelFormat::Rgb => { + TexturePixelFormat::Rgb => { let mut iter = source.chunks_exact(3).map(|p| Rgba8Pixel { r: p[0], g: p[1], @@ -444,7 +444,7 @@ impl ImageInner { }); slice.fill_with(|| iter.next().unwrap()); } - PixelFormat::RgbaPremultiplied => { + TexturePixelFormat::RgbaPremultiplied => { let mut iter = source.chunks_exact(4).map(|p| Rgba8Pixel { r: p[0], g: p[1], @@ -453,7 +453,7 @@ impl ImageInner { }); slice.fill_with(|| iter.next().unwrap()); } - PixelFormat::Rgba => { + TexturePixelFormat::Rgba => { let mut iter = source.chunks_exact(4).map(|p| { let a = p[3]; Rgba8Pixel { @@ -465,7 +465,7 @@ impl ImageInner { }); slice.fill_with(|| iter.next().unwrap()); } - PixelFormat::AlphaMap => { + TexturePixelFormat::AlphaMap => { let col = t.color.to_argb_u8(); let mut iter = source.iter().map(|p| { let a = *p as u32 * col.alpha as u32; @@ -478,7 +478,7 @@ impl ImageInner { }); slice.fill_with(|| iter.next().unwrap()); } - PixelFormat::SignedDistanceField => { + TexturePixelFormat::SignedDistanceField => { todo!("converting from a signed distance field to an image") } }; diff --git a/internal/core/software_renderer.rs b/internal/core/software_renderer.rs index 7fae4caa959..9ae03fa1501 100644 --- a/internal/core/software_renderer.rs +++ b/internal/core/software_renderer.rs @@ -18,9 +18,7 @@ pub use self::minimal_software_window::MinimalSoftwareWindow; use self::scene::*; use crate::api::PlatformError; use crate::graphics::rendering_metrics_collector::{RefreshMode, RenderingMetricsCollector}; -use crate::graphics::{ - BorderRadius, PixelFormat, Rgba8Pixel, SharedImageBuffer, SharedPixelBuffer, -}; +use crate::graphics::{BorderRadius, Rgba8Pixel, SharedImageBuffer, SharedPixelBuffer}; use crate::item_rendering::{ CachedRenderingData, DirtyRegion, PartialRenderingState, RenderBorderRectangle, RenderImage, RenderRectangle, @@ -82,7 +80,7 @@ impl RenderingRotation { matches!(self, Self::Rotate90 | Self::Rotate180) } /// Angle of the rotation in degrees - fn angle(self) -> f32 { + pub fn angle(self) -> f32 { match self { RenderingRotation::NoRotation => 0., RenderingRotation::Rotate90 => 90., @@ -365,6 +363,32 @@ fn region_line_ranges( next_validity } +mod target_pixel_buffer; + +#[cfg(feature = "experimental")] +pub use target_pixel_buffer::{CompositionMode, TargetPixelBuffer, Texture, TexturePixelFormat}; + +#[cfg(not(feature = "experimental"))] +use target_pixel_buffer::{CompositionMode, TexturePixelFormat}; + +struct TargetPixelSlice<'a, T> { + data: &'a mut [T], + pixel_stride: usize, +} + +impl<'a, T: TargetPixel> target_pixel_buffer::TargetPixelBuffer for TargetPixelSlice<'a, T> { + type TargetPixel = T; + + fn line_slice(&mut self, line_number: usize) -> &mut [Self::TargetPixel] { + let offset = line_number * self.pixel_stride; + &mut self.data[offset..offset + self.pixel_stride] + } + + fn num_lines(&self) -> usize { + self.data.len() / self.pixel_stride + } +} + /// A Renderer that do the rendering in software /// /// The renderer can remember what items needs to be redrawn from the previous iteration. @@ -454,6 +478,34 @@ impl SoftwareRenderer { /// Returns the physical dirty region for this frame, excluding the extra_draw_region, /// in the window frame of reference. It is affected by the screen rotation. pub fn render(&self, buffer: &mut [impl TargetPixel], pixel_stride: usize) -> PhysicalRegion { + self.render_buffer_impl(&mut TargetPixelSlice { data: buffer, pixel_stride }) + } + + /// Render the window to the given frame buffer. + /// + /// The renderer uses a cache internally and will only render the part of the window + /// which are dirty. The `extra_draw_region` is an extra region which will also + /// be rendered. (eg: the previous dirty region in case of double buffering) + /// This function returns the region that was rendered. + /// + /// The buffer's line slices need to be wide enough to if the `width` of the screen and the line count the `height`, + /// or the `height` and `width` swapped if the screen is rotated by 90°. + /// + /// Returns the physical dirty region for this frame, excluding the extra_draw_region, + /// in the window frame of reference. It is affected by the screen rotation. + #[cfg(feature = "experimental")] + pub fn render_into_buffer(&self, buffer: &mut impl TargetPixelBuffer) -> PhysicalRegion { + self.render_buffer_impl(buffer) + } + + fn render_buffer_impl( + &self, + buffer: &mut impl target_pixel_buffer::TargetPixelBuffer, + ) -> PhysicalRegion { + let pixel_stride = buffer.line_slice(0).len(); + let num_lines = buffer.num_lines(); + let buffer_pixel_count = num_lines * pixel_stride; + let Some(window) = self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade()) else { return Default::default(); @@ -471,31 +523,26 @@ impl SoftwareRenderer { window_item.background(), ) } else if rotation.is_transpose() { - (euclid::size2((buffer.len() / pixel_stride) as _, pixel_stride as _), Brush::default()) + (euclid::size2(num_lines as _, pixel_stride as _), Brush::default()) } else { - (euclid::size2(pixel_stride as _, (buffer.len() / pixel_stride) as _), Brush::default()) + (euclid::size2(pixel_stride as _, num_lines as _), Brush::default()) }; if size.is_empty() { return Default::default(); } assert!( if rotation.is_transpose() { - pixel_stride >= size.height as usize && buffer.len() >= (size.width as usize * pixel_stride + size.height as usize) - pixel_stride + pixel_stride >= size.height as usize && buffer_pixel_count >= (size.width as usize * pixel_stride + size.height as usize) - pixel_stride } else { - pixel_stride >= size.width as usize && buffer.len() >= (size.height as usize * pixel_stride + size.width as usize) - pixel_stride + pixel_stride >= size.width as usize && buffer_pixel_count >= (size.height as usize * pixel_stride + size.width as usize) - pixel_stride }, - "buffer of size {} with stride {pixel_stride} is too small to handle a window of size {size:?}", buffer.len() + "buffer of size {} with stride {pixel_stride} is too small to handle a window of size {size:?}", buffer_pixel_count ); let buffer_renderer = SceneBuilder::new( size, factor, window_inner, - RenderToBuffer { - buffer, - stride: pixel_stride, - dirty_range_cache: vec![], - dirty_region: Default::default(), - }, + RenderToBuffer { buffer, dirty_range_cache: vec![], dirty_region: Default::default() }, rotation, ); let mut renderer = self.partial_rendering_state.create_partial_renderer(buffer_renderer); @@ -541,26 +588,13 @@ impl SoftwareRenderer { }; drop(i); - let mut bg = TargetPixel::background(); // TODO: gradient background - TargetPixel::blend(&mut bg, background.color().into()); - let mut line = 0; - while let Some(next) = region_line_ranges( - &dirty_region, - line, - &mut renderer.actual_renderer.processor.dirty_range_cache, - ) { - for l in line..next { - for r in &renderer.actual_renderer.processor.dirty_range_cache { - renderer.actual_renderer.processor.buffer[l as usize * pixel_stride..] - [r.start as usize..r.end as usize] - .fill(bg); - } - } - line = next; - } - renderer.actual_renderer.processor.dirty_region = dirty_region.clone(); + renderer.actual_renderer.processor.process_rectangle_impl( + PhysicalRect::from_size(euclid::Size2D::new(size.width, size.height)), + background.color().into(), + CompositionMode::Source, + ); for (component, origin) in components { crate::item_rendering::render_component_items( @@ -1082,18 +1116,35 @@ trait ProcessScene { fn process_gradient(&mut self, geometry: PhysicalRect, gradient: GradientCommand); } -struct RenderToBuffer<'a, TargetPixel> { - buffer: &'a mut [TargetPixel], - stride: usize, +struct RenderToBuffer<'a, TargetPixelBuffer> { + buffer: &'a mut TargetPixelBuffer, dirty_range_cache: Vec>, dirty_region: PhysicalRegion, } -impl RenderToBuffer<'_, T> { +impl RenderToBuffer<'_, B> { fn foreach_ranges( &mut self, geometry: &PhysicalRect, - mut f: impl FnMut(i16, &mut [T], i16, i16), + mut f: impl FnMut(i16, &mut [B::TargetPixel], i16, i16), + ) { + self.foreach_region(geometry, |buffer, rect, extra_left_clip, extra_right_clip| { + for l in rect.min_y()..rect.max_y() { + f( + l, + &mut buffer.line_slice(l as usize) + [rect.min_x() as usize..rect.max_x() as usize], + extra_left_clip, + extra_right_clip, + ); + } + }); + } + + fn foreach_region( + &mut self, + geometry: &PhysicalRect, + mut f: impl FnMut(&mut B, PhysicalRect, i16, i16), ) { let mut line = geometry.min_y(); while let Some(mut next) = @@ -1112,14 +1163,12 @@ impl RenderToBuffer<'_, T> { let extra_left_clip = begin - geometry.origin.x; let extra_right_clip = geometry.origin.x + geometry.size.width - end; - for l in line..next { - f( - l, - &mut self.buffer[l as usize * self.stride..][begin as usize..end as usize], - extra_left_clip, - extra_right_clip, - ); - } + let region = PhysicalRect { + origin: PhysicalPoint::new(begin, line), + size: PhysicalSize::new(end - begin, next - line), + }; + + f(&mut self.buffer, region, extra_left_clip, extra_right_clip); } if next == geometry.max_y() { break; @@ -1129,20 +1178,84 @@ impl RenderToBuffer<'_, T> { } fn process_texture_impl(&mut self, geometry: PhysicalRect, texture: SceneTexture<'_>) { - self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, extra_right_clip| { - draw_functions::draw_texture_line( - &geometry, - PhysicalLength::new(line), - &texture, - buffer, - extra_left_clip, - extra_right_clip, - ); + self.foreach_region(&geometry, |buffer, rect, extra_left_clip, extra_right_clip| { + if !buffer.draw_texture( + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + geometry.origin.y, + target_pixel_buffer::Texture { + bytes: texture.data, + pixel_format: texture.format, + pixel_stride: texture.pixel_stride, + width: texture.source_size().width as u16, + height: texture.source_size().height as u16, + delta_x: texture.extra.dx.0, + delta_y: texture.extra.dy.0, + source_offset_x: texture.extra.off_x.0, + source_offset_y: texture.extra.off_y.0, + }, + texture.extra.colorize.as_argb_encoded(), + texture.extra.alpha, + texture.extra.rotation, + CompositionMode::default(), + ) { + let begin = rect.min_x(); + let end = rect.max_x(); + for l in rect.min_y()..rect.max_y() { + draw_functions::draw_texture_line( + &geometry, + PhysicalLength::new(l), + &texture, + &mut buffer.line_slice(l as usize)[begin as usize..end as usize], + extra_left_clip, + extra_right_clip, + ); + } + } }); } + + fn process_rectangle_impl( + &mut self, + geometry: PhysicalRect, + color: PremultipliedRgbaColor, + composition_mode: CompositionMode, + ) { + if !self.buffer.fill_rectangle( + geometry.origin.x, + geometry.origin.y, + geometry.size.width, + geometry.size.height, + color, + composition_mode, + ) { + self.foreach_region(&geometry, |buffer, rect, _extra_left_clip, _extra_right_clip| { + let begin = rect.min_x(); + let end = rect.max_x(); + for l in rect.min_y()..rect.max_y() { + match composition_mode { + CompositionMode::Source => { + let mut fill_col = B::TargetPixel::background(); + B::TargetPixel::blend(&mut fill_col, color); + buffer.line_slice(l as usize)[begin as usize..end as usize] + .fill(fill_col) + } + CompositionMode::SourceOver => ::blend_slice( + &mut buffer.line_slice(l as usize)[begin as usize..end as usize], + color, + ), + } + } + }) + }; + } } -impl ProcessScene for RenderToBuffer<'_, T> { +impl> ProcessScene + for RenderToBuffer<'_, B> +{ fn process_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>) { self.process_texture_impl(geometry, texture) } @@ -1153,8 +1266,24 @@ impl ProcessScene for RenderToBuffer<'_, T> { } fn process_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor) { - self.foreach_ranges(&geometry, |_line, buffer, _extra_left_clip, _extra_right_clip| { - TargetPixel::blend_slice(buffer, color); + self.foreach_region(&geometry, |buffer, rect, _extra_left_clip, _extra_right_clip| { + if !buffer.fill_rectangle( + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height, + color, + CompositionMode::default(), + ) { + let begin = rect.min_x(); + let end = rect.max_x(); + for l in rect.min_y()..rect.max_y() { + ::blend_slice( + &mut buffer.line_slice(l as usize)[begin as usize..end as usize], + color, + ) + } + } }); } @@ -1379,7 +1508,8 @@ impl<'a, T: ProcessScene> SceneBuilder<'a, T> { let bpp = t.format.bpp(); let color = if colorize.alpha() > 0 { colorize } else { t.color }; - let alpha = if colorize.alpha() > 0 || t.format == PixelFormat::AlphaMap { + let alpha = if colorize.alpha() > 0 || t.format == TexturePixelFormat::AlphaMap + { color.alpha() as u16 * global_alpha_u16 / 255 } else { global_alpha_u16 @@ -1553,7 +1683,7 @@ impl<'a, T: ProcessScene> SceneBuilder<'a, T> { SceneTexture { data, pixel_stride, - format: PixelFormat::AlphaMap, + format: TexturePixelFormat::AlphaMap, extra: SceneTextureExtra { colorize: color, // color already is mixed with global alpha @@ -1584,7 +1714,7 @@ impl<'a, T: ProcessScene> SceneBuilder<'a, T> { SceneTexture { data, pixel_stride, - format: PixelFormat::SignedDistanceField, + format: TexturePixelFormat::SignedDistanceField, extra: SceneTextureExtra { colorize: color, // color already is mixed with global alpha diff --git a/internal/core/software_renderer/draw_functions.rs b/internal/core/software_renderer/draw_functions.rs index acb19e42cbe..07666f790da 100644 --- a/internal/core/software_renderer/draw_functions.rs +++ b/internal/core/software_renderer/draw_functions.rs @@ -7,7 +7,7 @@ //! on the line buffer use super::{Fixed, PhysicalLength, PhysicalRect}; -use crate::graphics::{PixelFormat, Rgb8Pixel}; +use crate::graphics::{Rgb8Pixel, TexturePixelFormat}; use crate::lengths::{PointLengths, SizeLengths}; use crate::Color; use derive_more::{Add, Mul, Sub}; @@ -183,7 +183,7 @@ pub(super) fn draw_texture_line( fn fetch_blend_pixel( line_buffer: &mut [impl TargetPixel], - format: PixelFormat, + format: TexturePixelFormat, data: &[u8], alpha: u8, color: Color, @@ -191,10 +191,10 @@ pub(super) fn draw_texture_line( mut pos: impl FnMut(usize) -> (usize, u8, u8), ) { match format { - PixelFormat::Rgb => { + TexturePixelFormat::Rgb => { for pix in line_buffer { let pos = pos(3).0; - let p = &data[pos..pos + 3]; + let p: &[u8] = &data[pos..pos + 3]; if alpha == 0xff { *pix = TargetPixel::from_rgb(p[0], p[1], p[2]); } else { @@ -204,7 +204,7 @@ pub(super) fn draw_texture_line( } } } - PixelFormat::Rgba => { + TexturePixelFormat::Rgba => { if color.alpha() == 0 { for pix in line_buffer { let pos = pos(4).0; @@ -231,7 +231,7 @@ pub(super) fn draw_texture_line( } } } - PixelFormat::RgbaPremultiplied => { + TexturePixelFormat::RgbaPremultiplied => { if color.alpha() > 0 { for pix in line_buffer { let pos = pos(4).0; @@ -267,7 +267,7 @@ pub(super) fn draw_texture_line( } } } - PixelFormat::AlphaMap => { + TexturePixelFormat::AlphaMap => { for pix in line_buffer { let pos = pos(1).0; let c = PremultipliedRgbaColor::premultiply(Color::from_argb_u8( @@ -279,7 +279,7 @@ pub(super) fn draw_texture_line( pix.blend(c); } } - PixelFormat::SignedDistanceField => { + TexturePixelFormat::SignedDistanceField => { const RANGE: i32 = 6; let factor = (362 * 256 / delta.0) * RANGE; // 362 ≃ 255 * sqrt(2) for pix in line_buffer { diff --git a/internal/core/software_renderer/scene.rs b/internal/core/software_renderer/scene.rs index 68c62f7416b..cecde03ec5d 100644 --- a/internal/core/software_renderer/scene.rs +++ b/internal/core/software_renderer/scene.rs @@ -7,7 +7,7 @@ use super::{ Fixed, PhysicalBorderRadius, PhysicalLength, PhysicalPoint, PhysicalRect, PhysicalRegion, PhysicalSize, PremultipliedRgbaColor, RenderingRotation, }; -use crate::graphics::{PixelFormat, SharedImageBuffer}; +use crate::graphics::{SharedImageBuffer, TexturePixelFormat}; use crate::lengths::{PointLengths as _, SizeLengths as _}; use crate::Color; use alloc::rc::Rc; @@ -284,7 +284,7 @@ pub enum SceneCommand { pub struct SceneTexture<'a> { /// This should have a size so that the entire slice is ((height - 1) * pixel_stride + width) * bpp pub data: &'a [u8], - pub format: PixelFormat, + pub format: TexturePixelFormat, /// number of pixels between two lines in the source pub pixel_stride: u16, @@ -294,7 +294,7 @@ pub struct SceneTexture<'a> { impl SceneTexture<'_> { pub fn source_size(&self) -> PhysicalSize { let mut len = self.data.len(); - if self.format == PixelFormat::SignedDistanceField { + if self.format == TexturePixelFormat::SignedDistanceField { len -= 1; } else { len /= self.format.bpp(); @@ -355,27 +355,27 @@ impl SharedBufferCommand { SharedBufferData::SharedImage(SharedImageBuffer::RGB8(b)) => SceneTexture { data: &b.as_bytes()[start * 3..end * 3], pixel_stride: stride as u16, - format: PixelFormat::Rgb, + format: TexturePixelFormat::Rgb, extra: self.extra, }, SharedBufferData::SharedImage(SharedImageBuffer::RGBA8(b)) => SceneTexture { data: &b.as_bytes()[start * 4..end * 4], pixel_stride: stride as u16, - format: PixelFormat::Rgba, + format: TexturePixelFormat::Rgba, extra: self.extra, }, SharedBufferData::SharedImage(SharedImageBuffer::RGBA8Premultiplied(b)) => { SceneTexture { data: &b.as_bytes()[start * 4..end * 4], pixel_stride: stride as u16, - format: PixelFormat::RgbaPremultiplied, + format: TexturePixelFormat::RgbaPremultiplied, extra: self.extra, } } SharedBufferData::AlphaMap { data, width } => SceneTexture { data: &data[start..end], pixel_stride: *width, - format: PixelFormat::AlphaMap, + format: TexturePixelFormat::AlphaMap, extra: self.extra, }, } diff --git a/internal/core/software_renderer/target_pixel_buffer.rs b/internal/core/software_renderer/target_pixel_buffer.rs new file mode 100644 index 00000000000..868c37c27f9 --- /dev/null +++ b/internal/core/software_renderer/target_pixel_buffer.rs @@ -0,0 +1,82 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +use super::*; + +pub use crate::graphics::{CompositionMode, TexturePixelFormat}; + +/// This structure describes the properties of a texture for blending with [`TargetPixelBuffer::draw_texture`]. +#[allow(dead_code)] +#[non_exhaustive] +pub struct Texture<'a> { + /// A reference to the pixel bytes of the texture. These bytes are in the format specified by `pixel_format`. + pub bytes: &'a [u8], + /// The pixel format of the texture. + pub pixel_format: TexturePixelFormat, + /// The number of pixels per horizontal line of the texture. + pub pixel_stride: u16, + /// The width of the texture in pixels. + pub width: u16, + /// The height of the texture in pixels. + pub height: u16, + /// The delta to apply to the source x coordinate between pixels when drawing the texture. + /// This is used when scaling the texture. The delta is specified in 8:8 fixed point format. + pub delta_x: u16, + /// The delta to apply to the source y coordinate between pixels when drawing the texture. + /// This is used when scaling the texture. The delta is specified in 8:8 fixed point format. + pub delta_y: u16, + /// The offset within the texture to start reading pixels from in the x direction. The + /// offset is specified in 12:4 fixed point format. + pub source_offset_x: u16, + /// The offset within the texture to start reading pixels from in the y direction. The + /// offset is specified in 12:4 fixed point format. + pub source_offset_y: u16, +} + +/// This trait represents access to a buffer of pixels the software renderer can render into, as well +/// as certain operations that the renderer will try to delegate to this trait. Implement these functions +/// to delegate rendering further to hardware-provided 2D acceleration units, such as DMA2D or PXP. +pub trait TargetPixelBuffer { + /// The pixel type the buffer represents. + type TargetPixel: TargetPixel; + + /// Returns a slice of pixels for the given line. + fn line_slice(&mut self, line_numer: usize) -> &mut [Self::TargetPixel]; + + /// Returns the number of lines the buffer has. This is typically the height in pixels. + fn num_lines(&self) -> usize; + + /// Fills the buffer with a rectangle at the specified position with the given size and the + /// provided color. Returns true if the operation was successful; false if it could not be + /// implemented and instead the software renderer needs to draw the rectangle. + fn fill_rectangle( + &mut self, + _x: i16, + _y: i16, + _width: i16, + _height: i16, + _color: PremultipliedRgbaColor, + _composition_mode: CompositionMode, + ) -> bool { + false + } + + /// Draw a texture into the buffer at the specified position with the given size and + /// colorized if needed. Returns true if the operation was successful; false if it could not be + /// implemented and instead the software renderer needs to draw the texture + fn draw_texture( + &mut self, + _x: i16, + _y: i16, + _width: i16, + _height: i16, + _span_y: i16, + _src_texture: Texture<'_>, + _colorize: u32, + _alpha: u8, + _rotation: RenderingRotation, + _composition_mode: CompositionMode, + ) -> bool { + false + } +}